import { apiFetch, apiPatchJson, apiPostJson, type ApiSettings } from "@govoplan/core-webui"; export type OrganizationUnitItem = { id: string; tenant_id: string; unit_type_id?: string | null; parent_id?: string | null; slug: string; name: string; description?: string | null; is_active: boolean; settings: Record; created_at: string; updated_at: string; }; export type OrganizationFunctionItem = { id: string; tenant_id: string; function_type_id?: string | null; organization_unit_id: string; slug: string; name: string; description?: string | null; delegable: boolean; act_in_place_allowed: boolean; is_active: boolean; settings: Record; created_at: string; updated_at: string; }; export type OrganizationModel = { units: OrganizationUnitItem[]; functions: OrganizationFunctionItem[]; }; export type IdentityOption = { id: string; display_name?: string | null; external_subject?: string | null; source: string; primary_account_id?: string | null; account_ids: string[]; status: string; }; export type IdentityListResponse = { identities: IdentityOption[]; }; export type TypedGroupItem = { id: string; tenant_id: string; key: string; name: string; group_type: string; description?: string | null; status: "active" | "inactive"; source_provider: string; source_resource_type?: string | null; source_resource_id?: string | null; source_revision?: string | null; properties: Record; provenance: Record; revision: number; created_at?: string | null; updated_at?: string | null; }; export type TypedGroupList = { groups: TypedGroupItem[]; total: number; }; export type TypedGroupPayload = { key: string; name: string; group_type: string; description?: string | null; source_provider?: string; source_resource_type?: string | null; source_resource_id?: string | null; source_revision?: string | null; properties?: Record; provenance?: Record; }; export type TypedGroupUpdatePayload = Partial & { base_revision: number; status?: "active" | "inactive"; }; export type IdentityRelationshipItem = { id: string; tenant_id: string; relationship_kind: string; subject_identity_id: string; target_group_id?: string | null; related_identity_id?: string | null; role?: string | null; valid_from?: string | null; valid_until?: string | null; status: "active" | "revoked"; revoked_at?: string | null; revoked_by?: string | null; revocation_reason?: string | null; expired_event_at?: string | null; source_provider: string; source_resource_type?: string | null; source_resource_id?: string | null; source_revision?: string | null; properties: Record; provenance: Record; revision: number; created_at?: string | null; updated_at?: string | null; }; export type IdentityRelationshipList = { relationships: IdentityRelationshipItem[]; total: number; }; export type IdentityRelationshipPayload = { relationship_kind: string; subject_identity_id: string; target_group_id?: string | null; related_identity_id?: string | null; role?: string | null; valid_from?: string | null; valid_until?: string | null; source_provider?: string; source_resource_type?: string | null; source_resource_id?: string | null; source_revision?: string | null; properties?: Record; provenance?: Record; }; export type IdentityRelationshipUpdatePayload = Omit< Partial, "subject_identity_id" > & { base_revision: number; }; export type IdentityRelationshipDecisionItem = { relationship: IdentityRelationshipItem; included: boolean; code: string; explanation: string; identity_status?: string | null; }; export type TypedGroupMembershipResolution = { group: TypedGroupItem; effective_at: string; decisions: IdentityRelationshipDecisionItem[]; identity_ids: string[]; }; export type OrganizationFunctionAssignmentItem = { id: string; tenant_id: string; identity_id: string; account_id?: string | null; function_id: string; organization_unit_id: string; applies_to_subunits: boolean; source: string; delegated_from_assignment_id?: string | null; acting_for_account_id?: string | null; valid_from?: string | null; valid_until?: string | null; is_active: boolean; settings: Record; created_at: string; updated_at: string; }; export type OrganizationFunctionAssignmentList = { assignments: OrganizationFunctionAssignmentItem[]; total?: number; page?: number; page_size?: number; pages?: number; }; export type IdmSettings = { tenant_id: string; require_assignment_change_requests: boolean; audit_detail_level: "summary" | "standard" | "full"; change_retention_days?: number | null; settings: Record; created_at?: string | null; updated_at?: string | null; }; export type OrganizationFunctionAssignmentPayload = { identity_id: string; function_id: string; account_id?: string | null; applies_to_subunits?: boolean; source?: string; delegated_from_assignment_id?: string | null; acting_for_account_id?: string | null; valid_from?: string | null; valid_until?: string | null; is_active?: boolean; settings?: Record; change_request_id?: string | null; governance_override_reason?: string | null; governance_override_evidence?: string[]; }; export type IdmSettingsPayload = Partial>; export type FunctionAssignmentChangeKind = "request" | "grant"; export type FunctionAssignmentChangeAction = "approve" | "reject" | "accept" | "request_changes" | "respond" | "withdraw" | "recover"; export type FunctionAssignmentChangeEvent = { id: string; sequence: number; action: string; from_state?: string | null; to_state: string; actor_account_id?: string | null; actor_identity_id?: string | null; comment?: string | null; evidence: string[]; policy_decision: Record; workflow_step_id?: string | null; details: Record; created_at: string; }; export type FunctionAssignmentChange = { id: string; tenant_id: string; kind: FunctionAssignmentChangeKind; state: string; profile: string; function_id: string; organization_unit_id: string; candidate_identity_id: string; candidate_account_id?: string | null; initiator_account_id: string; initiator_identity_id?: string | null; justification: string; evidence: string[]; requested_valid_from?: string | null; requested_valid_until?: string | null; required_steps: string[]; completed_steps: string[]; policy_decision: Record; workflow_definition_revision?: number | null; workflow_definition_hash?: string | null; workflow_instance_id?: string | null; resulting_assignment_id?: string | null; expires_at?: string | null; review_deadline_at?: string | null; escalated_at?: string | null; escalation_from_state?: string | null; escalation_target_function_id?: string | null; outcome_reason?: string | null; resource_revision: number; etag: string; metadata: Record; events: FunctionAssignmentChangeEvent[]; available_actions: FunctionAssignmentChangeAction[]; availability_reason?: string | null; created_at: string; updated_at: string; }; export type FunctionAssignmentChangeList = { changes: FunctionAssignmentChange[]; total: number; page: number; page_size: number; pages: number; }; export type FunctionAssignmentChangePayload = { kind: FunctionAssignmentChangeKind; function_id: string; candidate_identity_id: string; candidate_account_id?: string | null; justification: string; evidence?: string[]; requested_valid_from?: string | null; requested_valid_until?: string | null; applies_to_subunits?: boolean; assignment_source?: "governance" | "delegated"; represented_assignment_id?: string | null; idempotency_key: string; metadata?: Record; }; export function getOrganizationModel(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/organizations/model"); } export async function getOrganizationFunctionAssignments(settings: ApiSettings): Promise { const pageSize = 500; const assignments: OrganizationFunctionAssignmentItem[] = []; let total = 0; for (let page = 1; ; page += 1) { const response = await apiFetch( settings, `/api/v1/idm/organization-function-assignments?page=${page}&page_size=${pageSize}` ); assignments.push(...response.assignments); total = response.total ?? assignments.length; if (page >= (response.pages ?? 1)) { return { assignments, total, page: 1, page_size: assignments.length, pages: 1 }; } } } export function getIdmSettings(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/idm/settings"); } export function patchIdmSettings(settings: ApiSettings, payload: IdmSettingsPayload): Promise { return apiPatchJson(settings, "/api/v1/idm/settings", payload); } export function searchOrganizationIdentityOptions( settings: ApiSettings, query = "", limit = 50, signal?: AbortSignal ): Promise { const params = new URLSearchParams(); const trimmed = query.trim(); if (trimmed) params.set("query", trimmed); params.set("limit", String(limit)); return apiFetch(settings, `/api/v1/idm/organization-identities?${params.toString()}`, { signal }); } export function getTypedGroups( settings: ApiSettings, options: { query?: string; includeInactive?: boolean; limit?: number; signal?: AbortSignal } = {} ): Promise { const params = new URLSearchParams(); if (options.query?.trim()) params.set("query", options.query.trim()); if (options.includeInactive) params.set("include_inactive", "true"); params.set("limit", String(options.limit ?? 1000)); return apiFetch(settings, `/api/v1/idm/typed-groups?${params.toString()}`, { signal: options.signal }); } export function createTypedGroup(settings: ApiSettings, payload: TypedGroupPayload): Promise { return apiPostJson(settings, "/api/v1/idm/typed-groups", payload); } export function patchTypedGroup( settings: ApiSettings, groupId: string, payload: TypedGroupUpdatePayload ): Promise { return apiPatchJson(settings, `/api/v1/idm/typed-groups/${encodeURIComponent(groupId)}`, payload); } export function getIdentityRelationships( settings: ApiSettings, options: { includeRevoked?: boolean; identityId?: string; groupId?: string; relationshipKind?: string; limit?: number } = {} ): Promise { const params = new URLSearchParams(); if (options.includeRevoked) params.set("include_revoked", "true"); if (options.identityId) params.set("identity_id", options.identityId); if (options.groupId) params.set("group_id", options.groupId); if (options.relationshipKind) params.set("relationship_kind", options.relationshipKind); params.set("limit", String(options.limit ?? 1000)); return apiFetch(settings, `/api/v1/idm/relationships?${params.toString()}`); } export function createIdentityRelationship( settings: ApiSettings, payload: IdentityRelationshipPayload ): Promise { return apiPostJson(settings, "/api/v1/idm/relationships", payload); } export function patchIdentityRelationship( settings: ApiSettings, relationshipId: string, payload: IdentityRelationshipUpdatePayload ): Promise { return apiPatchJson(settings, `/api/v1/idm/relationships/${encodeURIComponent(relationshipId)}`, payload); } export function revokeIdentityRelationship( settings: ApiSettings, relationship: Pick, reason: string ): Promise { return apiPostJson(settings, `/api/v1/idm/relationships/${encodeURIComponent(relationship.id)}/revoke`, { base_revision: relationship.revision, reason }); } export function resolveTypedGroupMemberships( settings: ApiSettings, groupId: string, options: { effectiveAt?: string; relationshipKinds?: string[] } = {} ): Promise { const params = new URLSearchParams(); if (options.effectiveAt) params.set("effective_at", options.effectiveAt); for (const kind of options.relationshipKinds ?? ["member"]) params.append("relationship_kind", kind); return apiFetch( settings, `/api/v1/idm/typed-groups/${encodeURIComponent(groupId)}/memberships?${params.toString()}` ); } export function createOrganizationFunctionAssignment( settings: ApiSettings, payload: OrganizationFunctionAssignmentPayload ): Promise { return apiPostJson(settings, "/api/v1/idm/organization-function-assignments", payload); } export function patchOrganizationFunctionAssignment( settings: ApiSettings, id: string, payload: Partial ): Promise { return apiPatchJson(settings, `/api/v1/idm/organization-function-assignments/${encodeURIComponent(id)}`, payload); } export function getFunctionAssignmentChanges(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/idm/function-assignment-changes?page_size=200"); } export function getFunctionAssignmentChange(settings: ApiSettings, id: string): Promise { return apiFetch(settings, `/api/v1/idm/function-assignment-changes/${encodeURIComponent(id)}`); } export function createFunctionAssignmentChange( settings: ApiSettings, payload: FunctionAssignmentChangePayload ): Promise { return apiPostJson(settings, "/api/v1/idm/function-assignment-changes", payload); } export function actOnFunctionAssignmentChange( settings: ApiSettings, change: FunctionAssignmentChange, action: FunctionAssignmentChangeAction, comment?: string ): Promise { return apiFetch( settings, `/api/v1/idm/function-assignment-changes/${encodeURIComponent(change.id)}/actions`, { method: "POST", headers: { "If-Match": change.etag }, body: JSON.stringify({ action, base_revision: change.resource_revision, comment: comment?.trim() || null, evidence: [] }) } ); }