import { useEffect, useMemo, useState } from "react";
import { Edit3, Plus, RefreshCw, Save, ShieldCheck, Trash2, UserRoundKey } from "lucide-react";
import {
  Button,
  Card,
  ConnectionTree,
  ConfirmDialog,
  CredentialPanel,
  DisabledActionTooltip,
  DismissibleAlert,
  Dialog,
  FormField,
  ActionBlockerHint,
  AdvancedOptionsPanel,
  LoadingFrame,
  mergeDeltaRows,
  SegmentedControl,
  StatusBadge,
  TableActionGroup,
  ToggleSwitch,
  useDeltaWatermarks,
  useUnsavedDraftGuard,
  type ApiSettings,
  type ConnectionTreeColumn,
  type FileConnectorScope,
  type FileConnectorTargetOption,
  i18nMessage } from
"@govoplan/core-webui";
import {
  browseFileConnectorProfile,
  createFileConnectorCredential,
  createFileConnectorProfile,
  deactivateFileConnectorCredential,
  deactivateFileConnectorProfile,
  discoverFileConnectorEndpoint,
  fetchFileConnectorSettingsDelta,
  updateFileConnectorPolicy,
  updateFileConnectorCredential,
  updateFileConnectorProfile,
  type FileConnectorCredential,
  type FileConnectorCredentialPayload,
  type FileConnectorCredentialUpdatePayload,
  type FileConnectorPolicyResponse,
  type FileConnectorProfile,
  type FileConnectorProfilePayload,
  type FileConnectorProfileUpdatePayload } from
"../../api/files";

type ConnectorScope = FileConnectorScope;
type Provider = FileConnectorProfilePayload["provider"];
type CredentialMode = NonNullable<FileConnectorProfilePayload["credential_mode"]>;
type PolicyMode = "allow-provider" | "allowed-paths" | "deny-provider";

type ConnectorTreeRow =
{kind: "profile";id: string;profile: FileConnectorProfile;} |
{kind: "credential";id: string;credential: FileConnectorCredential;profile: FileConnectorProfile;};

type ConnectorProfileDraft = {
  id: string;
  label: string;
  provider: Provider;
  endpointUrl: string;
  basePath: string;
  enabled: boolean;
  credentialProfileId: string;
  credentialMode: CredentialMode;
  username: string;
  password: string;
  token: string;
  secretRef: string;
  canBrowse: boolean;
  canImport: boolean;
  canSync: boolean;
  policyMode: PolicyMode;
  allowedPaths: string;
  deniedPaths: string;
  requireSigning: boolean;
  authProtocol: string;
  browseProtocol: string;
  metadataJson: string;
  clearPassword: boolean;
  clearToken: boolean;
};

type ConnectorCredentialDraft = {
  id: string;
  label: string;
  provider: Provider | "";
  enabled: boolean;
  credentialMode: CredentialMode;
  username: string;
  password: string;
  token: string;
  secretRef: string;
  policyMode: PolicyMode;
  allowedPaths: string;
  deniedPaths: string;
  metadataJson: string;
  clearPassword: boolean;
  clearToken: boolean;
};

type CentralConnectorPolicyDraft = {
  allowedConnectors: string;
  allowedCredentials: string;
  allowedProviders: string;
  allowedPaths: string;
  allowedUrls: string;
  deniedConnectors: string;
  deniedCredentials: string;
  deniedProviders: string;
  deniedPaths: string;
  deniedUrls: string;
  allowLowerConnectionLimits: boolean;
  allowLowerCredentialLimits: boolean;
  allowLowerProviderLimits: boolean;
  allowLowerPathLimits: boolean;
  allowLowerUrlLimits: boolean;
};

const PROVIDERS: Array<{id: Provider;label: string;}> = [
{ id: "webdav", label: "i18n:govoplan-files.webdav.521b4c8b" },
{ id: "nextcloud", label: "i18n:govoplan-files.nextcloud.aab73a3d" },
{ id: "seafile", label: "i18n:govoplan-files.seafile.600192cc" },
{ id: "smb", label: "i18n:govoplan-files.smb.515d2f88" },
{ id: "s3", label: "S3" },
{ id: "sharepoint", label: "SharePoint" },
{ id: "onedrive", label: "OneDrive" },
{ id: "nfs", label: "i18n:govoplan-files.nfs.db121865" },
{ id: "dms", label: "i18n:govoplan-files.dms.477e5652" },
{ id: "generic", label: "i18n:govoplan-files.generic.ff7613e5" }];


const ENDPOINT_PLACEHOLDERS: Record<Provider, string> = {
  webdav: "http://127.0.0.1:9083/",
  nextcloud: "http://127.0.0.1:9081/remote.php/dav/files/admin/",
  seafile: "http://127.0.0.1:9082/",
  smb: "smb://127.0.0.1:1445/files",
  s3: "http://127.0.0.1:9000",
  sharepoint: "https://graph.microsoft.com/v1.0/sites/{site-id}/drives/{drive-id}",
  onedrive: "https://graph.microsoft.com/v1.0/drives/{drive-id}",
  nfs: "nfs://127.0.0.1/export",
  dms: "https://dms.example.test",
  generic: "https://files.example.test"
};

export default function FileConnectorSettingsPanel({
  settings,
  scopeType,
  scopeId = null,
  targetOptions = [],
  targetLabel = "i18n:govoplan-files.target.61ad50a9",
  title,
  canWrite








}: {settings: ApiSettings;scopeType: ConnectorScope;scopeId?: string | null;targetOptions?: FileConnectorTargetOption[];targetLabel?: string;title?: string;canWrite: boolean;}) {
  const [profiles, setProfiles] = useState<FileConnectorProfile[]>([]);
  const [credentials, setCredentials] = useState<FileConnectorCredential[]>([]);
  const [selectedTargetId, setSelectedTargetId] = useState(scopeId || targetOptions[0]?.id || "");
  const [policyResponse, setPolicyResponse] = useState<FileConnectorPolicyResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [draft, setDraft] = useState<ConnectorProfileDraft | null>(null);
  const [savedDraftKey, setSavedDraftKey] = useState("");
  const [credentialDraft, setCredentialDraft] = useState<ConnectorCredentialDraft | null>(null);
  const [savedCredentialDraftKey, setSavedCredentialDraftKey] = useState("");
  const [credentialAttachProfileId, setCredentialAttachProfileId] = useState<string | null>(null);
  const [policyDraft, setPolicyDraft] = useState<CentralConnectorPolicyDraft>(emptyCentralPolicyDraft());
  const [savedPolicyDraftKey, setSavedPolicyDraftKey] = useState(centralPolicyDraftKey(emptyCentralPolicyDraft()));
  const [editingProfileId, setEditingProfileId] = useState<string | null>(null);
  const [editingCredentialId, setEditingCredentialId] = useState<string | null>(null);
  const [pendingDeactivate, setPendingDeactivate] = useState<FileConnectorProfile | null>(null);
  const [pendingCredentialDeactivate, setPendingCredentialDeactivate] = useState<FileConnectorCredential | null>(null);
  const [testingProfileId, setTestingProfileId] = useState<string | null>(null);
  const [testResults, setTestResults] = useState<Record<string, {ok: boolean;message: string;}>>({});
  const [discoveringProfile, setDiscoveringProfile] = useState(false);
  const [profileDiscoveryResult, setProfileDiscoveryResult] = useState<{ok: boolean;message: string;} | null>(null);
  const [testingCredentialLogin, setTestingCredentialLogin] = useState(false);
  const [credentialLoginResult, setCredentialLoginResult] = useState<{ok: boolean;message: string;} | null>(null);
  const [message, setMessage] = useState("");
  const [error, setError] = useState("");
  const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();

  const requiresTarget = scopeType === "user" || scopeType === "group";
  const targetSelectionRequired = requiresTarget && !scopeId;
  const hasSelectableTarget = targetOptions.length > 0;
  const activeScopeId = scopeId || selectedTargetId || null;
  const deltaKey = `files:connectors:${scopeType}:${activeScopeId ?? ""}`;
  const scopeReady = !requiresTarget || Boolean(activeScopeId);
  const targetEmptyText = i18nMessage("i18n:govoplan-files.no_value_available", { value0: targetPluralLabel(scopeType, targetLabel) });
  const profileDirty = Boolean(draft) && connectorProfileDraftKey(draft) !== savedDraftKey;
  const credentialDirty = Boolean(credentialDraft) && connectorCredentialDraftKey(credentialDraft) !== savedCredentialDraftKey;
  const policyDirty = scopeReady && centralPolicyDraftKey(policyDraft) !== savedPolicyDraftKey;

  useUnsavedDraftGuard({
    dirty: profileDirty || credentialDirty || policyDirty,
    onSave: saveDirtyDrafts,
    onDiscard: discardDirtyDrafts
  });

  useEffect(() => {
    if (scopeId) {
      setSelectedTargetId(scopeId);
      return;
    }
    if (targetOptions.length > 0 && !targetOptions.some((option) => option.id === selectedTargetId)) {
      setSelectedTargetId(targetOptions[0].id);
      return;
    }
    if (targetOptions.length === 0 && selectedTargetId) setSelectedTargetId("");
  }, [scopeId, selectedTargetId, targetOptions]);

  const scopedProfiles = useMemo(
    () => profiles.filter((profile) => connectorBelongsToEditableScope(profile, scopeType, activeScopeId)),
    [activeScopeId, profiles, scopeType]
  );
  const scopedCredentials = useMemo(
    () => credentials.filter((credential) => connectorBelongsToEditableScope(credential, scopeType, activeScopeId)),
    [activeScopeId, credentials, scopeType]
  );
  const selectableCredentials = useMemo(
    () => credentials.filter((credential) =>
      connectorBelongsToEditableScope(credential, scopeType, activeScopeId) ||
      credential.source_kind === "credential_envelope" &&
      reusableCredentialAvailableAtScope(credential, scopeType, activeScopeId)
    ),
    [activeScopeId, credentials, scopeType]
  );

  useEffect(() => {
    resetDeltaWatermark(deltaKey);
    void loadProfiles(true);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [settings.apiBaseUrl, settings.apiKey, settings.accessToken, scopeType, activeScopeId, scopeReady, deltaKey, resetDeltaWatermark]);

  async function loadProfiles(forceFull = false) {
    setLoading(true);
    setError("");
    if (!scopeReady) {
      setProfiles([]);
      setCredentials([]);
      setPolicyResponse(null);
      setPolicyDraft(emptyCentralPolicyDraft());
      setSavedPolicyDraftKey(centralPolicyDraftKey(emptyCentralPolicyDraft()));
      resetDeltaWatermark(deltaKey);
      setLoading(false);
      return;
    }
    try {
      let nextProfiles = forceFull ? [] : profiles;
      let nextCredentials = forceFull ? [] : credentials;
      let nextPolicyResponse = forceFull ? null : policyResponse;
      let nextWatermark = forceFull ? null : getDeltaWatermark(deltaKey);
      let hasMore = false;
      do {
        const response = await fetchFileConnectorSettingsDelta(settings, {
          scope_type: scopeType,
          scope_id: activeScopeId,
          include_disabled: true,
          since: nextWatermark
        });
        nextProfiles = response.full ?
        response.profiles :
        mergeDeltaRows(nextProfiles, response.profiles, response.deleted, (profile) => profile.id, {
          deletedResourceType: "file_connector_profile",
          sort: sortConnectorProfiles
        });
        nextCredentials = response.full ?
        response.credentials :
        mergeDeltaRows(nextCredentials, response.credentials, response.deleted, (credential) => credential.id, {
          deletedResourceType: "file_connector_credential",
          sort: sortConnectorCredentials
        });
        if (response.policy) nextPolicyResponse = response.policy;
        nextWatermark = response.watermark ?? null;
        hasMore = response.has_more;
      } while (hasMore);
      setProfiles(nextProfiles);
      setCredentials(nextCredentials);
      setPolicyResponse(nextPolicyResponse);
      if (nextPolicyResponse) {
        const nextPolicyDraft = centralPolicyDraftFromPolicy(nextPolicyResponse.policy);
        setPolicyDraft(nextPolicyDraft);
        setSavedPolicyDraftKey(centralPolicyDraftKey(nextPolicyDraft));
      }
      setDeltaWatermark(deltaKey, nextWatermark);
    } catch (err) {
      resetDeltaWatermark(deltaKey);
      setError(errorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  function startCreate() {
    setEditingProfileId(null);
    const nextDraft = emptyDraft(scopeType);
    setDraft(nextDraft);
    setSavedDraftKey(connectorProfileDraftKey(nextDraft));
    setProfileDiscoveryResult(null);
    setMessage("");
    setError("");
  }

  function startCredentialCreate(profile: FileConnectorProfile) {
    setEditingCredentialId(null);
    setCredentialAttachProfileId(profile.id);
    const next = emptyCredentialDraft(scopeType);
    next.id = `${profile.id}-credentials`;
    next.label = `${profile.label} credentials`;
    next.provider = knownProvider(profile.provider);
    next.credentialMode = knownCredentialMode(profile.credential_mode);
    setCredentialDraft(next);
    setSavedCredentialDraftKey(connectorCredentialDraftKey(next));
    setCredentialLoginResult(null);
    setMessage("");
    setError("");
  }

  function startEdit(profile: FileConnectorProfile) {
    setEditingProfileId(profile.id);
    const nextDraft = draftFromProfile(profile, scopeType);
    setDraft(nextDraft);
    setSavedDraftKey(connectorProfileDraftKey(nextDraft));
    setProfileDiscoveryResult(null);
    setMessage("");
    setError("");
  }

  function startCredentialEdit(credential: FileConnectorCredential) {
    setEditingCredentialId(credential.id);
    setCredentialAttachProfileId(null);
    const nextDraft = credentialDraftFromCredential(credential, scopeType);
    setCredentialDraft(nextDraft);
    setSavedCredentialDraftKey(connectorCredentialDraftKey(nextDraft));
    setCredentialLoginResult(null);
    setMessage("");
    setError("");
  }

  function patchDraft(patch: Partial<ConnectorProfileDraft>) {
    setDraft((current) => current ? { ...current, ...patch } : current);
  }

  function patchCredentialDraft(patch: Partial<ConnectorCredentialDraft>) {
    setCredentialLoginResult(null);
    setCredentialDraft((current) => current ? { ...current, ...patch } : current);
  }

  function patchCredentialValues(patch: {username?: string | null;password?: string | null;}) {
    const next: Partial<ConnectorCredentialDraft> = {};
    if (patch.username !== undefined) next.username = String(patch.username ?? "");
    if (patch.password !== undefined) next.password = String(patch.password ?? "");
    patchCredentialDraft(next);
  }

  function patchCredentialToken(patch: {password?: string | null;}) {
    if (patch.password !== undefined) patchCredentialDraft({ token: String(patch.password ?? "") });
  }

  function patchPolicyDraft(patch: Partial<CentralConnectorPolicyDraft>) {
    setPolicyDraft((current) => ({ ...current, ...patch }));
  }

  function closeProfileDialog() {
    if (saving) return;
    setDraft(null);
    setEditingProfileId(null);
    setProfileDiscoveryResult(null);
  }

  function closeCredentialDialog() {
    if (saving) return;
    setCredentialDraft(null);
    setEditingCredentialId(null);
    setCredentialAttachProfileId(null);
    setCredentialLoginResult(null);
  }

  async function savePolicyDraft(): Promise<boolean> {
    if (!canWrite || !scopeReady) return false;
    setSaving(true);
    setError("");
    try {
      const response = await updateFileConnectorPolicy(settings, scopeType, centralPolicyFromDraft(policyDraft), activeScopeId);
      setPolicyResponse(response);
      const savedPolicyDraft = centralPolicyDraftFromPolicy(response.policy);
      setPolicyDraft(savedPolicyDraft);
      setSavedPolicyDraftKey(centralPolicyDraftKey(savedPolicyDraft));
      setMessage(i18nMessage("i18n:govoplan-files.value_connector_policy_saved.430d4eca", { value0: scopeLabel(scopeType) }));
      return true;
    } catch (err) {
      setError(errorMessage(err));
      return false;
    } finally {
      setSaving(false);
    }
  }

  async function saveDraft(): Promise<boolean> {
    if (!draft || !canWrite || !scopeReady) return false;
    const label = draft.label.trim();
    const id = draft.id.trim();
    if (!label || !editingProfileId && !id) {
      setError("i18n:govoplan-files.profile_id_and_label_are_required.6ad85df1");
      return false;
    }

    let metadata: Record<string, unknown>;
    try {
      metadata = metadataFromDraft(draft);
    } catch (err) {
      setError(errorMessage(err));
      return false;
    }

    const capabilities = [
    draft.canBrowse ? "browse" : "",
    draft.canImport ? "import" : "",
    draft.canSync ? "sync" : ""].
    filter(Boolean);
    const sharedPayload = {
      label,
      provider: draft.provider,
      endpoint_url: cleanOrNull(draft.endpointUrl),
      base_path: cleanOrNull(draft.basePath),
      enabled: draft.enabled,
      credential_profile_id: cleanOrNull(credentialProfileIdForDraft(draft, selectableCredentials)),
      credential_mode: draft.credentialMode,
      capabilities,
      policy: policyFromDraft(draft),
      metadata
    };

    setSaving(true);
    setError("");
    try {
      if (editingProfileId) {
        const payload: FileConnectorProfileUpdatePayload = {
          ...sharedPayload,
          clear_password: draft.clearPassword,
          clear_token: draft.clearToken
        };
        await updateFileConnectorProfile(settings, editingProfileId, payload);
      } else {
        const payload: FileConnectorProfilePayload = {
          id,
          scope_type: scopeType,
          scope_id: activeScopeId,
          ...sharedPayload
        };
        await createFileConnectorProfile(settings, payload);
      }
      setMessage(editingProfileId ? "i18n:govoplan-files.file_connection_saved.68c16ee9" : "i18n:govoplan-files.file_connection_created.bdf6e1f6");
      setDraft(null);
      setSavedDraftKey("");
      setEditingProfileId(null);
      await loadProfiles();
      return true;
    } catch (err) {
      setError(errorMessage(err));
      return false;
    } finally {
      setSaving(false);
    }
  }

  async function saveCredentialDraft(): Promise<boolean> {
    if (!credentialDraft || !canWrite || !scopeReady) return false;
    if (!editingCredentialId && !credentialAttachProfileId) {
      setError("Create credentials from a connection row so the credential is attached to a file connection.");
      return false;
    }
    const label = credentialDraft.label.trim();
    const id = credentialDraft.id.trim();
    if (!label || !editingCredentialId && !id) {
      setError("i18n:govoplan-files.credential_id_and_label_are_required.4cfd1ffd");
      return false;
    }

    let metadata: Record<string, unknown>;
    try {
      metadata = metadataFromJson(credentialDraft.metadataJson);
    } catch (err) {
      setError(errorMessage(err));
      return false;
    }

    const credentials = {
      username: cleanOrNull(credentialDraft.username),
      password: cleanOrUndefined(credentialDraft.password),
      token: cleanOrUndefined(credentialDraft.token),
      secret_ref: cleanOrNull(credentialDraft.secretRef)
    };
    const sharedPayload = {
      label,
      provider: credentialDraft.provider || null,
      enabled: credentialDraft.enabled,
      credential_mode: credentialDraft.credentialMode,
      credentials,
      policy: credentialPolicyFromDraft(credentialDraft),
      metadata
    };

    setSaving(true);
    setError("");
    try {
      if (editingCredentialId) {
        const payload: FileConnectorCredentialUpdatePayload = {
          ...sharedPayload,
          clear_password: credentialDraft.clearPassword,
          clear_token: credentialDraft.clearToken
        };
        await updateFileConnectorCredential(settings, editingCredentialId, payload);
      } else {
        const payload: FileConnectorCredentialPayload = {
          id,
          scope_type: scopeType,
          scope_id: activeScopeId,
          ...sharedPayload
        };
        await createFileConnectorCredential(settings, payload);
        if (credentialAttachProfileId) {
          await updateFileConnectorProfile(settings, credentialAttachProfileId, {
            credential_profile_id: id,
            credential_mode: credentialDraft.credentialMode
          });
        }
      }
      setMessage(editingCredentialId ? "i18n:govoplan-files.file_credential_saved.d6a1eef8" : "i18n:govoplan-files.file_credential_created.87c31882");
      setCredentialDraft(null);
      setSavedCredentialDraftKey("");
      setEditingCredentialId(null);
      setCredentialAttachProfileId(null);
      await loadProfiles();
      return true;
    } catch (err) {
      setError(errorMessage(err));
      return false;
    } finally {
      setSaving(false);
    }
  }

  async function saveDirtyDrafts(): Promise<boolean> {
    let ok = true;
    if (policyDirty) ok = await savePolicyDraft() && ok;
    if (credentialDirty) ok = await saveCredentialDraft() && ok;
    if (profileDirty) ok = await saveDraft() && ok;
    return ok;
  }

  function discardDirtyDrafts() {
    setDraft(null);
    setSavedDraftKey("");
    setEditingProfileId(null);
    setCredentialDraft(null);
    setSavedCredentialDraftKey("");
    setEditingCredentialId(null);
    setCredentialAttachProfileId(null);
    setPolicyDraft(JSON.parse(savedPolicyDraftKey) as CentralConnectorPolicyDraft);
  }

  async function confirmDeactivate() {
    if (!pendingDeactivate) return;
    const profile = pendingDeactivate;
    setSaving(true);
    setError("");
    try {
      await deactivateFileConnectorProfile(settings, profile.id);
      setMessage(i18nMessage("i18n:govoplan-files.file_connection_disabled_value.059a7de9", { value0: profile.label }));
      setPendingDeactivate(null);
      await loadProfiles();
    } catch (err) {
      setError(errorMessage(err));
    } finally {
      setSaving(false);
    }
  }

  async function confirmCredentialDeactivate() {
    if (!pendingCredentialDeactivate) return;
    const credential = pendingCredentialDeactivate;
    setSaving(true);
    setError("");
    try {
      await deactivateFileConnectorCredential(settings, credential.id);
      setMessage(i18nMessage("i18n:govoplan-files.file_credential_disabled_value.947538fd", { value0: credential.label }));
      setPendingCredentialDeactivate(null);
      await loadProfiles();
    } catch (err) {
      setError(errorMessage(err));
    } finally {
      setSaving(false);
    }
  }

  async function testBrowse(profile: FileConnectorProfile) {
    setTestingProfileId(profile.id);
    setTestResults((current) => ({ ...current, [profile.id]: { ok: true, message: "i18n:govoplan-files.testing.15ccc832" } }));
    try {
      const response = await browseFileConnectorProfile(settings, profile.id, { path: "" });
      setTestResults((current) => ({
        ...current,
        [profile.id]: { ok: true, message: i18nMessage("i18n:govoplan-files.browse_ok_value_item_value.921906fd", { value0: response.items.length, value1: response.items.length === 1 ? "" : "s" }) }
      }));
    } catch (err) {
      setTestResults((current) => ({ ...current, [profile.id]: { ok: false, message: errorMessage(err) } }));
    } finally {
      setTestingProfileId(null);
    }
  }

  async function discoverProfileEndpoint() {
    if (!draft) return;
    const endpointUrl = draft.endpointUrl.trim();
    if (!endpointUrl) {
      setProfileDiscoveryResult({ ok: false, message: "Enter a server URL before running discovery." });
      return;
    }
    setDiscoveringProfile(true);
    setProfileDiscoveryResult(null);
    try {
      const response = await discoverFileConnectorEndpoint(settings, {
        provider: draft.provider,
        endpoint_url: endpointUrl,
        base_path: cleanOrNull(draft.basePath),
        credential_mode: draft.credentialMode,
        credentials: {
          username: cleanOrNull(draft.username),
          password: cleanOrUndefined(draft.password),
          token: cleanOrUndefined(draft.token),
          secret_ref: cleanOrNull(draft.secretRef)
        },
        metadata: metadataFromDraft(draft)
      });
      if (response.endpoint_url) {
        patchDraft({
          endpointUrl: response.endpoint_url,
          basePath: response.base_path ?? draft.basePath,
          metadataJson: JSON.stringify(response.metadata ?? metadataFromDraft(draft), null, 2)
        });
      }
      setProfileDiscoveryResult({
        ok: response.status === "usable" || response.status === "found",
        message: response.message
      });
    } catch (err) {
      setProfileDiscoveryResult({ ok: false, message: errorMessage(err) });
    } finally {
      setDiscoveringProfile(false);
    }
  }

  async function testCredentialLogin() {
    if (!credentialDraft) return;
    const profile = credentialTestProfile;
    if (!profile) {
      setCredentialLoginResult({ ok: false, message: "Create or select a file connection before testing this credential." });
      return;
    }
    if (profile.provider !== "webdav" && profile.provider !== "nextcloud") {
      setCredentialLoginResult({ ok: false, message: "Credential login testing is currently available for WebDAV and Nextcloud connections." });
      return;
    }
    setTestingCredentialLogin(true);
    setCredentialLoginResult(null);
    try {
      const response = await discoverFileConnectorEndpoint(settings, {
        provider: knownProvider(profile.provider),
        endpoint_url: profile.endpoint_url || "",
        base_path: profile.base_path || "",
        credential_mode: credentialDraft.credentialMode,
        credentials: {
          username: cleanOrNull(credentialDraft.username),
          password: cleanOrUndefined(credentialDraft.password),
          token: cleanOrUndefined(credentialDraft.token),
          secret_ref: cleanOrNull(credentialDraft.secretRef)
        },
        metadata: profile.metadata ?? {},
        require_valid_credentials: true
      });
      const ok = response.status === "usable" || response.status === "found";
      setCredentialLoginResult({
        ok,
        message: ok ? "Login successful. The credentials can browse the selected connection." : response.message
      });
    } catch (err) {
      setCredentialLoginResult({ ok: false, message: errorMessage(err) });
    } finally {
      setTestingCredentialLogin(false);
    }
  }

  const panelTitle = title ?? i18nMessage("i18n:govoplan-files.value_file_connections", { value0: scopeLabel(scopeType) });
  const selectedCredentialIds = new Set(scopedProfiles.map((profile) => profile.credential_profile_id).filter((value): value is string => Boolean(value)));
  const firstCompatibleProfileByCredential = new Map<string, string>();
  for (const credential of selectableCredentials) {
    const selectedProfile = scopedProfiles.find((profile) => profile.credential_profile_id === credential.id);
    const fallbackProfile = selectedProfile ?? scopedProfiles.find((profile) => !selectedCredentialIds.has(credential.id) && credentialMatchesProfile(credential, profile));
    if (fallbackProfile) firstCompatibleProfileByCredential.set(credential.id, fallbackProfile.id);
  }
  const connectorRows: ConnectorTreeRow[] = scopedProfiles.map((profile) => ({ kind: "profile", id: `profile:${profile.id}`, profile }));
  const credentialAttachedProfile = credentialAttachProfileId ? scopedProfiles.find((profile) => profile.id === credentialAttachProfileId) ?? null : null;
  const credentialTestProfile = credentialDraft ?
  credentialAttachedProfile ??
  (editingCredentialId ? scopedProfiles.find((profile) => profile.credential_profile_id === editingCredentialId) ?? scopedProfiles.find((profile) => credentialDraft.provider ? profile.provider === credentialDraft.provider : true) ?? null : null) :
  null;
  const profileMissingFields = profileDraftMissingFields(draft, editingProfileId);
  const credentialMissingFields = credentialDraftMissingFields(credentialDraft, editingCredentialId, credentialAttachProfileId);
  const profileMissingRequired = profileMissingFields.length > 0;
  const credentialMissingRequired = credentialMissingFields.length > 0;
  const profileSaveDisabled = !draft || saving || profileMissingRequired;
  const credentialSaveDisabled = !credentialDraft || saving || credentialMissingRequired;
  const profileSaveTooltip = requiredFieldsTooltip(profileMissingFields);
  const credentialSaveTooltip = requiredFieldsTooltip(credentialMissingFields);
  const canDiscoverProfileEndpoint = Boolean(draft && (draft.provider === "webdav" || draft.provider === "nextcloud"));
  const credentialTestUnsupported = Boolean(credentialTestProfile && credentialTestProfile.provider !== "webdav" && credentialTestProfile.provider !== "nextcloud");
  const credentialTestDisabled = !credentialDraft || saving || testingCredentialLogin || !credentialTestProfile || credentialTestUnsupported;
  const credentialTestTooltip = credentialTestHelpText(credentialTestProfile, credentialTestUnsupported);
  const profileCredentialOptions = draft ? credentialOptionsForProfileDraft(selectableCredentials, draft) : [];
  const profileCredentialSelectValue = draft ? credentialProfileIdForDraft(draft, selectableCredentials) : "";
  const connectorColumns: ConnectionTreeColumn<ConnectorTreeRow>[] = [
  {
    id: "connection",
    header: "i18n:govoplan-files.connection_credential.178babe0",
    width: "minmax(260px, 1.5fr)",
    render: (row) => row.kind === "profile" ?
    <ConnectorProfileCell profile={row.profile} result={testResults[row.profile.id]} /> :
    <ConnectorCredentialCell credential={row.credential} selected={row.profile.credential_profile_id === row.credential.id} />
  },
  {
    id: "endpoint",
    header: "i18n:govoplan-files.endpoint.92ec6350",
    width: "minmax(190px, 1fr)",
    render: (row) => row.kind === "profile" ?
    <span>{row.profile.endpoint_url || "i18n:govoplan-files.no_endpoint.d0ea68c4"}</span> :
    <span>{row.credential.provider ? providerLabel(row.credential.provider) : "i18n:govoplan-files.any_provider.b3fc542f"}</span>
  },
  {
    id: "policy",
    header: "i18n:govoplan-files.policy.bb9cf141",
    width: "minmax(160px, 0.8fr)",
    render: (row) => <span>{connectorPolicySummary(row)}</span>
  },
  {
    id: "status",
    header: "i18n:govoplan-files.status.bae7d5be",
    width: "120px",
    render: (row) => {
      const enabled = row.kind === "profile" ? row.profile.enabled : row.credential.enabled;
      return <StatusBadge status={enabled ? "success" : "inactive"} label={enabled ? "i18n:govoplan-files.enabled.df174a3f" : "i18n:govoplan-files.disabled.f4f4473d"} />;
    }
  }];


  function connectorChildren(row: ConnectorTreeRow): ConnectorTreeRow[] {
    if (row.kind !== "profile") return [];
    return selectableCredentials.
    filter((credential) => firstCompatibleProfileByCredential.get(credential.id) === row.profile.id).
    map((credential) => ({ kind: "credential" as const, id: `credential:${row.profile.id}:${credential.id}`, credential, profile: row.profile }));
  }

  function renderConnectorActions(row: ConnectorTreeRow) {
    if (row.kind === "credential") {
      const readOnly = row.credential.source_kind !== "database";
      return <TableActionGroup actions={[
        { id: "edit", label: i18nMessage("i18n:govoplan-files.edit_value.fad75899", { value0: row.credential.label }), icon: <Edit3 size={15} />, disabled: !canWrite || readOnly || saving, onClick: () => startCredentialEdit(row.credential) },
        { id: "disable", label: i18nMessage("i18n:govoplan-files.disable_value.09485f7f", { value0: row.credential.label }), icon: <Trash2 size={15} />, variant: "danger", applicable: row.credential.enabled, disabled: !canWrite || readOnly || saving, onClick: () => setPendingCredentialDeactivate(row.credential) }
      ]} />;

    }
    const readOnly = row.profile.source_kind !== "database";
    return <TableActionGroup actions={[
      { id: "test", label: i18nMessage("i18n:govoplan-files.test_value.6a5d10a5", { value0: row.profile.label }), icon: <RefreshCw size={15} />, applicable: row.profile.enabled, disabled: saving || testingProfileId === row.profile.id, onClick: () => void testBrowse(row.profile) },
      { id: "add-credential", label: i18nMessage("i18n:govoplan-files.add_credential_for_value.0fa9c1fe", { value0: row.profile.label }), icon: <UserRoundKey size={15} />, variant: "primary", disabled: !canWrite || saving, onClick: () => startCredentialCreate(row.profile) },
      { id: "edit", label: i18nMessage("i18n:govoplan-files.edit_value.fad75899", { value0: row.profile.label }), icon: <Edit3 size={15} />, disabled: !canWrite || readOnly || saving, onClick: () => startEdit(row.profile) },
      { id: "disable", label: i18nMessage("i18n:govoplan-files.disable_value.09485f7f", { value0: row.profile.label }), icon: <Trash2 size={15} />, variant: "danger", applicable: row.profile.enabled, disabled: !canWrite || readOnly || saving, onClick: () => setPendingDeactivate(row.profile) }
    ]} />;

  }

  return (
    <div className="file-connector-settings-panel">
      {error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
      {message && !error && <DismissibleAlert tone="success" resetKey={message} floating>{message}</DismissibleAlert>}

      {!canWrite &&
      <ActionBlockerHint
        tone="info"
        reason={{
          summary: "You can review file connectors here, but cannot change them from this scope.",
          details: "Create, edit, disable, and policy-save actions need a role with connector administration rights.",
          requiredAction: "Ask an administrator to grant the required permission or make the change at a higher scope.",
          actor: "System or tenant administrator",
          target: "Administration > GLOBAL or TENANT > File Connectors"
        }} />
      }

      {targetSelectionRequired && !hasSelectableTarget &&
      <ActionBlockerHint
        tone="warning"
        reason={{
          summary: "No target is available for this file connector scope.",
          details: "User and group connector settings need a concrete user or group before connections and policies can be loaded.",
          requiredAction: "Create or select a user or group first.",
          actor: "Access administrator",
          target: targetPluralLabel(scopeType, targetLabel)
        }} />
      }

      {targetSelectionRequired &&
      <Card title={i18nMessage("i18n:govoplan-files.value_scope", { value0: targetLabel })}>
          <div className="settings-target-row">
            <FormField label={targetLabel}>
              <select value={selectedTargetId} onChange={(event) => setSelectedTargetId(event.target.value)} disabled={loading || saving || !hasSelectableTarget}>
                {!hasSelectableTarget && <option value="">{targetEmptyText}</option>}
                {targetOptions.map((option) => <option key={option.id} value={option.id}>{option.secondary ? i18nMessage("i18n:govoplan-files.value_value.c189e8bc", { value0: option.label, value1: option.secondary }) : option.label}</option>)}
              </select>
            </FormField>
          </div>
        </Card>
      }

      {scopeReady &&
      <>
      <Card
          title={panelTitle}
          actions={
          canWrite ?
          <div className="button-row compact-actions">
              <Button onClick={() => void loadProfiles()} disabled={loading}>{loading ? "i18n:govoplan-files.loading.b04ba49f" : "i18n:govoplan-files.reload.cce71553"}</Button>
              <Button variant="primary" onClick={startCreate} disabled={saving}><Plus size={16} aria-hidden="true" /> i18n:govoplan-files.new_connection.ac979fe4</Button>
            </div> :
          null
          }>

        <LoadingFrame loading={loading} label="i18n:govoplan-files.loading_file_connections.bd68f224">
          <ConnectionTree
            rows={connectorRows}
            columns={connectorColumns}
            getRowKey={(row) => row.id}
            getChildren={connectorChildren}
            renderActions={renderConnectorActions}
            emptyText="i18n:govoplan-files.no_file_connections_configured.3ef02049" />
        </LoadingFrame>
      </Card>

      <Card
          title={i18nMessage("i18n:govoplan-files.value_connector_policy.0bf7f53b", { value0: scopeLabel(scopeType) })}
          actions={canWrite ?
          <Button variant="primary" onClick={() => void savePolicyDraft()} disabled={saving || loading}>
            <ShieldCheck size={16} aria-hidden="true" /> {saving ? "i18n:govoplan-files.saving.ae7e8875" : "i18n:govoplan-files.save_policy.77d67ce3"}
          </Button> :
          null}>

        <LoadingFrame loading={loading} label="i18n:govoplan-files.loading_connector_policy.f12ab285">
          <>
            <div className="form-grid two">
              <FormField label="i18n:govoplan-files.allowed_connection_ids.0df854c8">
                <textarea value={policyDraft.allowedConnectors} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ allowedConnectors: event.target.value })} rows={3} placeholder={"tenant-webdav\nseafile-*"} />
              </FormField>
              <FormField label="i18n:govoplan-files.denied_connection_ids.a95218b4">
                <textarea value={policyDraft.deniedConnectors} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ deniedConnectors: event.target.value })} rows={3} placeholder={"legacy-*\nblocked-smb"} />
              </FormField>
              <FormField label="i18n:govoplan-files.allowed_credential_ids.efcaba2d">
                <textarea value={policyDraft.allowedCredentials} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ allowedCredentials: event.target.value })} rows={3} placeholder={"tenant-webdav-credentials\nshared-*"} />
              </FormField>
              <FormField label="i18n:govoplan-files.denied_credential_ids.b6838bc2">
                <textarea value={policyDraft.deniedCredentials} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ deniedCredentials: event.target.value })} rows={3} placeholder={"personal-*\nblocked-*"} />
              </FormField>
              <FormField label="i18n:govoplan-files.allowed_providers.82a9c23e">
                <textarea value={policyDraft.allowedProviders} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ allowedProviders: event.target.value })} rows={3} placeholder={"webdav\nnextcloud\nsmb"} />
              </FormField>
              <FormField label="i18n:govoplan-files.denied_providers.149b29f4">
                <textarea value={policyDraft.deniedProviders} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ deniedProviders: event.target.value })} rows={3} placeholder={"generic\nnfs"} />
              </FormField>
              <FormField label="i18n:govoplan-files.allowed_paths.c953b4be">
                <textarea value={policyDraft.allowedPaths} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ allowedPaths: event.target.value })} rows={3} placeholder={"GovOPlaN\nreports/*"} />
              </FormField>
              <FormField label="i18n:govoplan-files.denied_paths.d25e4315">
                <textarea value={policyDraft.deniedPaths} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ deniedPaths: event.target.value })} rows={3} placeholder={"private\narchive/closed"} />
              </FormField>
              <FormField label="i18n:govoplan-files.allowed_endpoint_urls.34cfa8e9">
                <textarea value={policyDraft.allowedUrls} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ allowedUrls: event.target.value })} rows={3} placeholder={"https://files.example.test/*\nsmb://127.0.0.1:1445/files"} />
              </FormField>
              <FormField label="i18n:govoplan-files.denied_endpoint_urls.1d8d4369">
                <textarea value={policyDraft.deniedUrls} disabled={saving || !canWrite} onChange={(event) => patchPolicyDraft({ deniedUrls: event.target.value })} rows={3} placeholder={"http://*\n*://legacy.example.test/*"} />
              </FormField>
            </div>
            <div className="file-connector-settings-section file-connector-policy-locks">
              <ToggleSwitch label="i18n:govoplan-files.lower_connection_limits.4f9838bc" checked={policyDraft.allowLowerConnectionLimits} disabled={saving || !canWrite} onChange={(allowLowerConnectionLimits) => patchPolicyDraft({ allowLowerConnectionLimits })} />
              <ToggleSwitch label="i18n:govoplan-files.lower_credential_limits.ec2b72bc" checked={policyDraft.allowLowerCredentialLimits} disabled={saving || !canWrite} onChange={(allowLowerCredentialLimits) => patchPolicyDraft({ allowLowerCredentialLimits })} />
              <ToggleSwitch label="i18n:govoplan-files.lower_provider_limits.5816270a" checked={policyDraft.allowLowerProviderLimits} disabled={saving || !canWrite} onChange={(allowLowerProviderLimits) => patchPolicyDraft({ allowLowerProviderLimits })} />
              <ToggleSwitch label="i18n:govoplan-files.lower_path_limits.1abcccb1" checked={policyDraft.allowLowerPathLimits} disabled={saving || !canWrite} onChange={(allowLowerPathLimits) => patchPolicyDraft({ allowLowerPathLimits })} />
              <ToggleSwitch label="i18n:govoplan-files.lower_endpoint_limits.3fd781d5" checked={policyDraft.allowLowerUrlLimits} disabled={saving || !canWrite} onChange={(allowLowerUrlLimits) => patchPolicyDraft({ allowLowerUrlLimits })} />
            </div>
            {policyResponse?.effective_policy_sources?.length ?
            <div className="file-connector-policy-sources">
                i18n:govoplan-files.effective_sources.9a576802 {policyResponse.effective_policy_sources.map((source) => i18nMessage("i18n:govoplan-files.value_value.c189e8bc", { value0: source.label, value1: source.applied_fields.join(", ") || "metadata" })).join(" · ")}
              </div> :

            <div className="file-connector-policy-sources muted">i18n:govoplan-files.effective_sources_none.9a7c407f</div>
            }
          </>
        </LoadingFrame>
      </Card>

      <Dialog
          open={Boolean(credentialDraft)}
          title={editingCredentialId ? "i18n:govoplan-files.edit_file_credential.bc49d44f" : "i18n:govoplan-files.new_file_credential.4c061082"}
          onClose={closeCredentialDialog}
          className="admin-dialog admin-dialog-wide file-connector-dialog adaptive-config-dialog"
          bodyClassName="adaptive-config-dialog-body"
          closeDisabled={saving}
          footer={credentialDraft ?
          <>
            <Button onClick={closeCredentialDialog} disabled={saving}>i18n:govoplan-files.cancel.77dfd213</Button>
            <DisabledActionTooltip reason={credentialSaveTooltip}>
              <Button variant="primary" onClick={() => void saveCredentialDraft()} disabled={credentialSaveDisabled}>
                <Save size={16} aria-hidden="true" /> {saving ? "i18n:govoplan-files.saving.ae7e8875" : "i18n:govoplan-files.save_credential.2c02a6d2"}
              </Button>
            </DisabledActionTooltip>
          </> :
          null}>

        {credentialDraft &&
          <div className="adaptive-config-form">
            <section className="adaptive-config-section">
              <header>
                <h3>Credential</h3>
                <p>Choose where this credential can be used and how it signs in.</p>
              </header>
              <div className="form-grid two">
                <FormField label="i18n:govoplan-files.credential_id.9432a6e1">
                  <input className={!editingCredentialId && !credentialDraft.id.trim() ? "field-input-missing" : undefined} aria-invalid={!editingCredentialId && !credentialDraft.id.trim() || undefined} value={credentialDraft.id} disabled={Boolean(editingCredentialId) || saving} onChange={(event) => patchCredentialDraft({ id: event.target.value })} placeholder={`${scopeType}-webdav-credentials`} />
                </FormField>
                <FormField label="i18n:govoplan-files.label.74341e3c">
                  <input className={!credentialDraft.label.trim() ? "field-input-missing" : undefined} aria-invalid={!credentialDraft.label.trim() || undefined} value={credentialDraft.label} disabled={saving} onChange={(event) => patchCredentialDraft({ label: event.target.value })} placeholder="i18n:govoplan-files.govoplan_webdav_credentials.bc696d69" />
                </FormField>
                {credentialAttachProfileId &&
                <FormField label="i18n:govoplan-files.connection_credential.178babe0">
                  <input value={credentialAttachedProfile ? `${credentialAttachedProfile.label} (${credentialAttachedProfile.id})` : credentialAttachProfileId} disabled readOnly />
                </FormField>
                }
                <div className="file-connector-provider-field">
                  <FormField label="i18n:govoplan-files.provider.7ceee3f3">
                    <SegmentedControl
                      className="file-connector-provider-switch"
                      role="group"
                      size="equal"
                      width="fill"
                      ariaLabel="i18n:govoplan-files.provider.7ceee3f3"
                      value={credentialDraft.provider}
                      disabled={saving || Boolean(credentialAttachProfileId)}
                      onChange={(provider) => patchCredentialDraft({ provider })}
                      options={[
                        { id: "", label: "i18n:govoplan-files.any_provider.b3fc542f" },
                        ...PROVIDERS.map((provider) => ({ id: provider.id, label: provider.label }))
                      ]}
                    />
                  </FormField>
                </div>
                <FormField label="i18n:govoplan-files.credential_mode.23fdd899">
                  <select value={credentialDraft.credentialMode} disabled={saving} onChange={(event) => patchCredentialDraft({ credentialMode: event.target.value as CredentialMode })}>
                    <option value="none">i18n:govoplan-files.none.6eef6648</option>
                    <option value="anonymous">i18n:govoplan-files.anonymous.9bed5104</option>
                    <option value="basic">i18n:govoplan-files.username_password.e8ba8896</option>
                    <option value="token">i18n:govoplan-files.token.a1141eb9</option>
                    <option value="secret_ref">i18n:govoplan-files.secret_reference.04ed2221</option>
                  </select>
                </FormField>
              </div>
              <div className="file-connector-settings-section">
                <ToggleSwitch label="i18n:govoplan-files.enabled.df174a3f" checked={credentialDraft.enabled} disabled={saving} onChange={(enabled) => patchCredentialDraft({ enabled })} />
                {editingCredentialId && <ToggleSwitch label="i18n:govoplan-files.clear_saved_password.7442260d" checked={credentialDraft.clearPassword} disabled={saving} onChange={(clearPassword) => patchCredentialDraft({ clearPassword })} />}
                {editingCredentialId && <ToggleSwitch label="i18n:govoplan-files.clear_saved_token.a9c670aa" checked={credentialDraft.clearToken} disabled={saving} onChange={(clearToken) => patchCredentialDraft({ clearToken })} />}
              </div>
            </section>

            <section className="adaptive-config-section">
              <header>
                <h3>Secret</h3>
                <p>The fields below change with the selected credential mode.</p>
              </header>
              {(credentialDraft.credentialMode === "none" || credentialDraft.credentialMode === "anonymous") ?
              <ActionBlockerHint
                tone="info"
                reason={{
                  summary: "This credential mode does not store a secret.",
                  details: "Use this only when the provider allows access without a username, password, token, or secret reference.",
                  requiredAction: "Change the credential mode above if this provider needs authentication.",
                  actor: "Connector administrator",
                  target: "Credential mode"
                }} /> :
              <div className="file-connector-secret-fields">
                {credentialDraft.credentialMode === "basic" &&
                <CredentialPanel
                  values={{ username: credentialDraft.username, password: credentialDraft.password }}
                  onChange={patchCredentialValues}
                  disabled={saving}
                  savedPassword={Boolean(editingCredentialId) && !credentialDraft.clearPassword}
                  savedPasswordPlaceholder="i18n:govoplan-files.leave_empty_to_keep_saved_password.6ec39f5e" />
                }
                {credentialDraft.credentialMode === "secret_ref" &&
                <>
                  <CredentialPanel
                    values={{ username: credentialDraft.username }}
                    onChange={patchCredentialValues}
                    disabled={saving}
                    showPassword={false} />
                  <div className="form-grid two">
                    <FormField label="i18n:govoplan-files.secret_reference.04ed2221">
                      <input value={credentialDraft.secretRef} disabled={saving} onChange={(event) => patchCredentialDraft({ secretRef: event.target.value })} />
                    </FormField>
                  </div>
                </>
                }
                {credentialDraft.credentialMode === "token" &&
                <CredentialPanel
                  values={{ password: credentialDraft.token }}
                  onChange={patchCredentialToken}
                  disabled={saving}
                  showUsername={false}
                  passwordLabel="i18n:govoplan-files.token.a1141eb9"
                  savedPassword={Boolean(editingCredentialId) && !credentialDraft.clearToken}
                  savedPasswordPlaceholder="i18n:govoplan-files.leave_empty_to_keep_saved_token.e725857b" />
                }
              </div>
              }
              <div className="button-row compact-actions">
                <DisabledActionTooltip reason={credentialTestDisabled ? credentialTestTooltip : ""}>
                  <Button type="button" onClick={() => void testCredentialLogin()} disabled={credentialTestDisabled}>
                    <RefreshCw size={16} aria-hidden="true" /> {testingCredentialLogin ? "Testing login" : "Test login"}
                  </Button>
                </DisabledActionTooltip>
              </div>
              {credentialLoginResult &&
              <DismissibleAlert tone={credentialLoginResult.ok ? "success" : "warning"} resetKey={credentialLoginResult.message}>
                {credentialLoginResult.message}
              </DismissibleAlert>
              }
            </section>

            <section className="adaptive-config-section">
              <header>
                <h3>Policy</h3>
                <p>Limit where lower levels may use this credential.</p>
              </header>
              <div className="form-grid two">
                <FormField label="i18n:govoplan-files.policy.bb9cf141">
                  <select value={credentialDraft.policyMode} disabled={saving} onChange={(event) => patchCredentialDraft({ policyMode: event.target.value as PolicyMode })}>
                    <option value="allow-provider">i18n:govoplan-files.can_use_this_credential.f4a41971</option>
                    <option value="allowed-paths">i18n:govoplan-files.must_stay_in_allowed_paths.dc5b4eb4</option>
                    <option value="deny-provider">i18n:govoplan-files.blocked_by_policy.8d971f86</option>
                  </select>
                </FormField>
                {credentialDraft.policyMode === "allowed-paths" &&
                <FormField label="i18n:govoplan-files.allowed_paths.c953b4be">
                  <textarea value={credentialDraft.allowedPaths} disabled={saving} onChange={(event) => patchCredentialDraft({ allowedPaths: event.target.value })} rows={3} placeholder={"GovOPlaN\nreports/*"} />
                </FormField>
                }
                <FormField label="i18n:govoplan-files.denied_paths.d25e4315">
                  <textarea value={credentialDraft.deniedPaths} disabled={saving} onChange={(event) => patchCredentialDraft({ deniedPaths: event.target.value })} rows={3} placeholder={"private\narchive/closed"} />
                </FormField>
              </div>
              <AdvancedOptionsPanel title="Credential metadata JSON" summary="For diagnostics or provider-specific compatibility flags. Normal setup should not need this.">
                <FormField label="i18n:govoplan-files.metadata_json.b0e4c283">
                  <textarea value={credentialDraft.metadataJson} disabled={saving} onChange={(event) => patchCredentialDraft({ metadataJson: event.target.value })} rows={5} spellCheck={false} />
                </FormField>
              </AdvancedOptionsPanel>
            </section>
          </div>
          }
      </Dialog>

      <Dialog
          open={Boolean(draft)}
          title={editingProfileId ? "i18n:govoplan-files.edit_file_connection.78698154" : "i18n:govoplan-files.new_file_connection.2ec6eb56"}
          onClose={closeProfileDialog}
          className="admin-dialog admin-dialog-wide file-connector-dialog adaptive-config-dialog"
          bodyClassName="adaptive-config-dialog-body"
          closeDisabled={saving}
          footer={draft ?
          <>
            <Button onClick={closeProfileDialog} disabled={saving}>i18n:govoplan-files.cancel.77dfd213</Button>
            <DisabledActionTooltip reason={profileSaveTooltip}>
              <Button variant="primary" onClick={() => void saveDraft()} disabled={profileSaveDisabled}>
                <Save size={16} aria-hidden="true" /> {saving ? "i18n:govoplan-files.saving.ae7e8875" : "i18n:govoplan-files.save_connection.07796d38"}
              </Button>
            </DisabledActionTooltip>
          </> :
          null}>

        {draft &&
          <div className="adaptive-config-form">
            <section className="adaptive-config-section">
              <header>
                <h3>Connection</h3>
                <p>Name the file server connection and choose the provider.</p>
              </header>
              <div className="form-grid two">
                <FormField label="i18n:govoplan-files.profile_id.25961d68">
                  <input className={!editingProfileId && !draft.id.trim() ? "field-input-missing" : undefined} aria-invalid={!editingProfileId && !draft.id.trim() || undefined} value={draft.id} disabled={Boolean(editingProfileId) || saving} onChange={(event) => patchDraft({ id: event.target.value })} placeholder={`${scopeType}-webdav`} />
                </FormField>
                <FormField label="i18n:govoplan-files.label.74341e3c">
                  <input className={!draft.label.trim() ? "field-input-missing" : undefined} aria-invalid={!draft.label.trim() || undefined} value={draft.label} disabled={saving} onChange={(event) => patchDraft({ label: event.target.value })} placeholder="i18n:govoplan-files.govoplan_files.b20752ed" />
                </FormField>
                <div className="file-connector-provider-field">
                  <FormField label="i18n:govoplan-files.provider.7ceee3f3">
                    <SegmentedControl
                      className="file-connector-provider-switch"
                      role="group"
                      size="equal"
                      width="fill"
                      ariaLabel="i18n:govoplan-files.provider.7ceee3f3"
                      value={draft.provider}
                      disabled={saving}
                      onChange={(provider) => patchDraft({ provider, credentialProfileId: "" })}
                      options={PROVIDERS}
                    />
                  </FormField>
                </div>
                <div className="field-block file-connector-settings-toggle-field">
                  <ToggleSwitch label="i18n:govoplan-files.enabled.df174a3f" checked={draft.enabled} disabled={saving} onChange={(enabled) => patchDraft({ enabled })} />
                </div>
              </div>
            </section>

            <section className="adaptive-config-section">
              <header>
                <h3>Location and credentials</h3>
                <p>Only fields relevant for the selected provider and credential mode are shown.</p>
              </header>
              <div className="form-grid two">
                <FormField label="i18n:govoplan-files.endpoint_url.65aaaa45">
                  <input value={draft.endpointUrl} disabled={saving} onChange={(event) => patchDraft({ endpointUrl: event.target.value })} placeholder={ENDPOINT_PLACEHOLDERS[draft.provider]} />
                </FormField>
                <FormField label="i18n:govoplan-files.base_path.6a4867ca">
                  <input value={draft.basePath} disabled={saving} onChange={(event) => patchDraft({ basePath: event.target.value })} placeholder="GovOPlaN" />
                </FormField>
                <FormField label="i18n:govoplan-files.credential_mode.23fdd899">
                  <select value={draft.credentialMode} disabled={saving} onChange={(event) => patchDraft({ credentialMode: event.target.value as CredentialMode })}>
                    <option value="none">i18n:govoplan-files.none.6eef6648</option>
                    <option value="anonymous">i18n:govoplan-files.anonymous.9bed5104</option>
                    <option value="basic">i18n:govoplan-files.username_password.e8ba8896</option>
                    <option value="token">i18n:govoplan-files.token.a1141eb9</option>
                    <option value="secret_ref">i18n:govoplan-files.secret_reference.04ed2221</option>
                  </select>
                </FormField>
                <FormField label="i18n:govoplan-files.credential.8bede3ea">
                  <select value={profileCredentialSelectValue} disabled={saving || profileCredentialOptions.length === 0} onChange={(event) => patchDraft({ credentialProfileId: event.target.value })}>
                    {profileCredentialOptions.length === 0 && <option value="">i18n:govoplan-files.no_saved_credential.30a5a951</option>}
                    {profileCredentialOptions.map((credential) => <option key={credential.id} value={credential.id}>{credential.label}</option>)}
                  </select>
                </FormField>
              </div>
              <div className="button-row compact-actions">
                <Button type="button" onClick={() => patchDraft({ endpointUrl: ENDPOINT_PLACEHOLDERS[draft.provider] })} disabled={saving}>Use provider example</Button>
                {canDiscoverProfileEndpoint &&
                <Button type="button" onClick={() => void discoverProfileEndpoint()} disabled={saving || discoveringProfile || !draft.endpointUrl.trim()}>
                  <RefreshCw size={16} aria-hidden="true" /> {discoveringProfile ? "Checking endpoint" : "Discover endpoint"}
                </Button>
                }
              </div>
              {profileDiscoveryResult &&
              <DismissibleAlert tone={profileDiscoveryResult.ok ? "success" : "warning"} resetKey={profileDiscoveryResult.message}>
                {profileDiscoveryResult.message}
              </DismissibleAlert>
              }
              <AdvancedOptionsPanel title="Protocol and compatibility options" summary="Change these only when the provider or network requires an override.">
                <div className="form-grid two">
                  <FormField label="i18n:govoplan-files.browse_protocol.d1f27c90">
                    <select value={draft.browseProtocol} disabled={saving} onChange={(event) => patchDraft({ browseProtocol: event.target.value })}>
                      <option value="">i18n:govoplan-files.native_default.ba32f7b6</option>
                      <option value="webdav">i18n:govoplan-files.webdav.521b4c8b</option>
                    </select>
                  </FormField>
                  {draft.provider === "smb" &&
                    <>
                      <FormField label="i18n:govoplan-files.smb_authentication.96f7cb83">
                        <select value={draft.authProtocol} disabled={saving} onChange={(event) => patchDraft({ authProtocol: event.target.value })}>
                          <option value="ntlm">i18n:govoplan-files.ntlm.026eac85</option>
                          <option value="kerberos">i18n:govoplan-files.kerberos.53c35fb7</option>
                        </select>
                      </FormField>
                      <div className="field-block file-connector-settings-toggle-field">
                        <ToggleSwitch label="i18n:govoplan-files.require_smb_signing.5168a83d" checked={draft.requireSigning} disabled={saving} onChange={(requireSigning) => patchDraft({ requireSigning })} />
                      </div>
                    </>
                    }
                </div>
              </AdvancedOptionsPanel>
            </section>

            <section className="adaptive-config-section">
              <header>
                <h3>Actions and policy</h3>
                <p>Choose which file operations are available and where this connection may be used.</p>
              </header>
              <div className="file-connector-settings-section">
                <ToggleSwitch label="i18n:govoplan-files.browse.2f3b5c55" checked={draft.canBrowse} disabled={saving} onChange={(canBrowse) => patchDraft({ canBrowse })} />
                <ToggleSwitch label="i18n:govoplan-files.import.d6fbc9d2" checked={draft.canImport} disabled={saving} onChange={(canImport) => patchDraft({ canImport })} />
                <ToggleSwitch label="i18n:govoplan-files.sync.905f6309" checked={draft.canSync} disabled={saving} onChange={(canSync) => patchDraft({ canSync })} />
              </div>
              <div className="form-grid two">
                <FormField label="i18n:govoplan-files.policy.bb9cf141">
                  <select value={draft.policyMode} disabled={saving} onChange={(event) => patchDraft({ policyMode: event.target.value as PolicyMode })}>
                    <option value="allow-provider">i18n:govoplan-files.can_use_this_connection.5091a74c</option>
                    <option value="allowed-paths">i18n:govoplan-files.must_stay_in_allowed_paths.dc5b4eb4</option>
                    <option value="deny-provider">i18n:govoplan-files.blocked_by_policy.8d971f86</option>
                  </select>
                </FormField>
                {draft.policyMode === "allowed-paths" &&
                <FormField label="i18n:govoplan-files.allowed_paths.c953b4be">
                  <textarea value={draft.allowedPaths} disabled={saving} onChange={(event) => patchDraft({ allowedPaths: event.target.value })} rows={3} placeholder={"GovOPlaN\nreports/*"} />
                </FormField>
                }
                <FormField label="i18n:govoplan-files.denied_paths.d25e4315">
                  <textarea value={draft.deniedPaths} disabled={saving} onChange={(event) => patchDraft({ deniedPaths: event.target.value })} rows={3} placeholder={"private\narchive/closed"} />
                </FormField>
              </div>
              <AdvancedOptionsPanel title="Connection metadata JSON" summary="For diagnostics or provider-specific compatibility flags. Normal setup should not need this.">
                <FormField label="i18n:govoplan-files.metadata_json.b0e4c283">
                  <textarea value={draft.metadataJson} disabled={saving} onChange={(event) => patchDraft({ metadataJson: event.target.value })} rows={5} spellCheck={false} />
                </FormField>
              </AdvancedOptionsPanel>
            </section>
          </div>
          }
      </Dialog>

        </>
      }

      <ConfirmDialog
        open={Boolean(pendingDeactivate)}
        title="i18n:govoplan-files.disable_file_connection.3fd940ae"
        message={i18nMessage("i18n:govoplan-files.disable_value_existing_linked_spaces_will_stop_b.8c1b0ac6", { value0: pendingDeactivate?.label || "i18n:govoplan-files.this_file_connection.edcde997" })}
        confirmLabel="i18n:govoplan-files.disable.9a7d4e06"
        tone="danger"
        busy={saving}
        onConfirm={() => void confirmDeactivate()}
        onCancel={() => setPendingDeactivate(null)} />

      <ConfirmDialog
        open={Boolean(pendingCredentialDeactivate)}
        title="i18n:govoplan-files.disable_file_credential.49bff614"
        message={i18nMessage("i18n:govoplan-files.disable_value_connections_using_it_will_stop_aut.fc3a1708", { value0: pendingCredentialDeactivate?.label || "i18n:govoplan-files.this_file_credential.695efb90" })}
        confirmLabel="i18n:govoplan-files.disable.9a7d4e06"
        tone="danger"
        busy={saving}
        onConfirm={() => void confirmCredentialDeactivate()}
        onCancel={() => setPendingCredentialDeactivate(null)} />

    </div>);

}

function ConnectorProfileCell({
  profile,
  result



}: {profile: FileConnectorProfile;result?: {ok: boolean;message: string;};}) {
  const readOnly = profile.source_kind !== "database";
  return (
    <div className="connection-tree-main">
      <strong>{profile.label}</strong>
      <div className="connection-tree-muted-line">
        {profile.id} · {providerLabel(profile.provider)} · {profile.base_path || "i18n:govoplan-files.root.e96857c5"}
        {profile.credential_profile_label ? i18nMessage("i18n:govoplan-files.value.48afe802", { value0: profile.credential_profile_label }) : ""}
        {readOnly ? "i18n:govoplan-files.bootstrap_settings.05e3df38" : ""}
      </div>
      {result && <div className={result.ok ? "file-connector-test-ok" : "file-connector-test-error"}>{result.message}</div>}
    </div>);

}

function ConnectorCredentialCell({
  credential,
  selected



}: {credential: FileConnectorCredential;selected: boolean;}) {
  return (
    <div className="connection-tree-main">
      <strong>{credential.label}</strong>
      <div className="connection-tree-muted-line">
        {credential.id} · {credential.credential_mode} · {credential.credentials_configured ? "i18n:govoplan-files.secret_configured.4dc0f095" : "i18n:govoplan-files.no_secret.33c1a339"}
        {selected ? "i18n:govoplan-files.selected.840fac38" : "i18n:govoplan-files.available.974948f2"}
      </div>
    </div>);

}

function connectorBelongsToEditableScope(
item: {scope_type: string;scope_id?: string | null;},
scopeType: ConnectorScope,
scopeId: string | null)
: boolean {
  if (item.scope_type !== scopeType) return false;
  if (scopeType === "system") return !item.scope_id;
  if (scopeType === "tenant") return true;
  return Boolean(scopeId) && item.scope_id === scopeId;
}

function credentialMatchesProfile(credential: FileConnectorCredential, profile: FileConnectorProfile): boolean {
  return !credential.provider || credential.provider === profile.provider;
}

function connectorPolicySummary(row: ConnectorTreeRow): string {
  const item = row.kind === "profile" ? row.profile : row.credential;
  const policy = item.policy_sources.find((source) => source.scope_type === item.scope_type && source.scope_id === item.scope_id)?.policy ??
  item.policy_sources[0]?.policy ??
  {};
  const denyProviders = policyRuleList(policy, "deny", "providers");
  const denyCredentials = policyRuleList(policy, "deny", "credentials");
  const allowPaths = policyRuleList(policy, "allow", "external_paths");
  const denyPaths = policyRuleList(policy, "deny", "external_paths");
  if (row.kind === "profile" && (denyProviders.includes("*") || denyProviders.includes(row.profile.provider))) return "i18n:govoplan-files.blocked.99613c74";
  if (row.kind === "credential" && (denyCredentials.includes("*") || denyCredentials.includes(row.credential.id))) return "i18n:govoplan-files.blocked.99613c74";
  if (allowPaths.length > 0) return "i18n:govoplan-files.path_limited.d32cbef0";
  if (denyPaths.length > 0) return "i18n:govoplan-files.deny_listed_paths.fcde62cc";
  return item.policy_sources.length > 0 ? "i18n:govoplan-files.allowed.77c7b490" : "i18n:govoplan-files.inherited.58823af0";
}

function scopeLabel(scopeType: ConnectorScope): string {
  if (scopeType === "system") return "i18n:govoplan-files.system.bc0792d8";
  if (scopeType === "tenant") return "i18n:govoplan-files.tenant.3ca93c78";
  if (scopeType === "user") return "i18n:govoplan-files.user.9f8a2389";
  return "i18n:govoplan-files.group.171a0606";
}

function targetPluralLabel(scopeType: ConnectorScope, fallback: string): string {
  if (scopeType === "user") return "i18n:govoplan-files.users";
  if (scopeType === "group") return "i18n:govoplan-files.groups";
  if (scopeType === "campaign") return "i18n:govoplan-files.campaigns";
  return fallback;
}

function emptyDraft(scopeType: ConnectorScope): ConnectorProfileDraft {
  return {
    id: `${scopeType}-webdav`,
    label: "",
    provider: "webdav",
    endpointUrl: "",
    basePath: "",
    enabled: true,
    credentialProfileId: "",
    credentialMode: "basic",
    username: "",
    password: "",
    token: "",
    secretRef: "",
    canBrowse: true,
    canImport: true,
    canSync: true,
    policyMode: "allow-provider",
    allowedPaths: "",
    deniedPaths: "",
    requireSigning: true,
    authProtocol: "ntlm",
    browseProtocol: "",
    metadataJson: "{}",
    clearPassword: false,
    clearToken: false
  };
}

function connectorProfileDraftKey(draft: ConnectorProfileDraft): string {
  return JSON.stringify(draft);
}

function draftFromProfile(profile: FileConnectorProfile, scopeType: ConnectorScope): ConnectorProfileDraft {
  const policy = profile.policy_sources.find((source) => source.scope_type === profile.scope_type && source.scope_id === profile.scope_id)?.policy ??
  profile.policy_sources[0]?.policy ??
  {};
  const allowPaths = policyRuleList(policy, "allow", "external_paths");
  const denyPaths = policyRuleList(policy, "deny", "external_paths");
  const denyProviders = policyRuleList(policy, "deny", "providers");
  const metadata = profile.metadata ?? {};
  return {
    ...emptyDraft(scopeType),
    id: profile.id,
    label: profile.label,
    provider: knownProvider(profile.provider),
    endpointUrl: profile.endpoint_url ?? "",
    basePath: profile.base_path ?? "",
    enabled: profile.enabled,
    credentialProfileId: profile.credential_profile_id ?? "",
    credentialMode: knownCredentialMode(profile.credential_mode),
    username: profile.username ?? "",
    canBrowse: profile.capabilities.includes("browse"),
    canImport: profile.capabilities.includes("import"),
    canSync: profile.capabilities.includes("sync"),
    policyMode: denyProviders.includes("*") || denyProviders.includes(profile.provider) ? "deny-provider" : allowPaths.length > 0 ? "allowed-paths" : "allow-provider",
    allowedPaths: allowPaths.join("\n"),
    deniedPaths: denyPaths.join("\n"),
    requireSigning: typeof metadata.require_signing === "boolean" ? metadata.require_signing : true,
    authProtocol: typeof metadata.auth_protocol === "string" && metadata.auth_protocol ? metadata.auth_protocol : "ntlm",
    browseProtocol: typeof metadata.browse_protocol === "string" ? metadata.browse_protocol : "",
    metadataJson: JSON.stringify(metadata, null, 2)
  };
}

function emptyCredentialDraft(scopeType: ConnectorScope): ConnectorCredentialDraft {
  return {
    id: `${scopeType}-webdav-credentials`,
    label: "",
    provider: "",
    enabled: true,
    credentialMode: "basic",
    username: "",
    password: "",
    token: "",
    secretRef: "",
    policyMode: "allow-provider",
    allowedPaths: "",
    deniedPaths: "",
    metadataJson: "{}",
    clearPassword: false,
    clearToken: false
  };
}

function connectorCredentialDraftKey(draft: ConnectorCredentialDraft): string {
  return JSON.stringify(draft);
}

function credentialOptionsForProfileDraft(credentials: FileConnectorCredential[], draft: ConnectorProfileDraft): FileConnectorCredential[] {
  return credentials.filter((credential) =>
    credential.id === draft.credentialProfileId ||
    credential.enabled &&
    (!credential.provider || credential.provider === draft.provider) &&
    reusableCredentialAllowsProfile(credential, draft.id)
  );
}

function reusableCredentialAvailableAtScope(
  credential: FileConnectorCredential,
  scopeType: ConnectorScope,
  scopeId: string | null
): boolean {
  if (connectorBelongsToEditableScope(credential, scopeType, scopeId)) return true;
  if (credential.source_kind !== "credential_envelope") return false;
  const inherited = credential.metadata?.inherit_to_lower_scopes === true;
  if (!inherited) return false;
  if (credential.scope_type === "system") return scopeType !== "system";
  if (credential.scope_type === "tenant") {
    return scopeType === "group" || scopeType === "user";
  }
  return false;
}

function reusableCredentialAllowsProfile(
  credential: FileConnectorCredential,
  profileId: string
): boolean {
  if (credential.source_kind !== "credential_envelope") return true;
  const refs = Array.isArray(credential.metadata?.allowed_server_refs)
    ? credential.metadata.allowed_server_refs.filter((value): value is string => typeof value === "string")
    : [];
  return refs.length === 0 || refs.includes(`files:${profileId}`);
}

function credentialProfileIdForDraft(draft: ConnectorProfileDraft, credentials: FileConnectorCredential[]): string {
  const options = credentialOptionsForProfileDraft(credentials, draft);
  if (draft.credentialProfileId && options.some((credential) => credential.id === draft.credentialProfileId)) {
    return draft.credentialProfileId;
  }
  return options[0]?.id ?? "";
}

function profileDraftMissingFields(draft: ConnectorProfileDraft | null, editingProfileId: string | null): string[] {
  if (!draft) return [];
  return [
    !editingProfileId && !draft.id.trim() ? "connection ID" : "",
    !draft.label.trim() ? "label" : ""
  ].filter(Boolean);
}

function credentialDraftMissingFields(
draft: ConnectorCredentialDraft | null,
editingCredentialId: string | null,
credentialAttachProfileId: string | null)
: string[] {
  if (!draft) return [];
  return [
    !editingCredentialId && !credentialAttachProfileId ? "connection" : "",
    !editingCredentialId && !draft.id.trim() ? "credential ID" : "",
    !draft.label.trim() ? "label" : ""
  ].filter(Boolean);
}

function requiredFieldsTooltip(fields: string[]): string {
  if (fields.length === 0) return "";
  return `Fill in ${humanList(fields)} before saving.`;
}

function credentialTestHelpText(profile: FileConnectorProfile | null, unsupported: boolean): string {
  if (!profile) return "Create the credential from a connection row, or attach it to a compatible connection before testing.";
  if (unsupported) return "Draft credential testing is currently available for WebDAV and Nextcloud connections.";
  return "";
}

function humanList(values: string[]): string {
  if (values.length <= 1) return values[0] ?? "";
  if (values.length === 2) return `${values[0]} and ${values[1]}`;
  return `${values.slice(0, -1).join(", ")}, and ${values[values.length - 1]}`;
}

function emptyCentralPolicyDraft(): CentralConnectorPolicyDraft {
  return {
    allowedConnectors: "",
    allowedCredentials: "",
    allowedProviders: "",
    allowedPaths: "",
    allowedUrls: "",
    deniedConnectors: "",
    deniedCredentials: "",
    deniedProviders: "",
    deniedPaths: "",
    deniedUrls: "",
    allowLowerConnectionLimits: true,
    allowLowerCredentialLimits: true,
    allowLowerProviderLimits: true,
    allowLowerPathLimits: true,
    allowLowerUrlLimits: true
  };
}

function centralPolicyDraftKey(draft: CentralConnectorPolicyDraft): string {
  return JSON.stringify(draft);
}

function centralPolicyDraftFromPolicy(policy: Record<string, unknown> | null | undefined): CentralConnectorPolicyDraft {
  const value = policy ?? {};
  return {
    ...emptyCentralPolicyDraft(),
    allowedConnectors: policyRuleList(value, "allow", "connectors").join("\n"),
    allowedCredentials: policyRuleList(value, "allow", "credentials").join("\n"),
    allowedProviders: policyRuleList(value, "allow", "providers").join("\n"),
    allowedPaths: policyRuleList(value, "allow", "external_paths").join("\n"),
    allowedUrls: policyRuleList(value, "allow", "external_urls").join("\n"),
    deniedConnectors: policyRuleList(value, "deny", "connectors").join("\n"),
    deniedCredentials: policyRuleList(value, "deny", "credentials").join("\n"),
    deniedProviders: policyRuleList(value, "deny", "providers").join("\n"),
    deniedPaths: policyRuleList(value, "deny", "external_paths").join("\n"),
    deniedUrls: policyRuleList(value, "deny", "external_urls").join("\n"),
    allowLowerConnectionLimits: lowerLevelAllowed(value, "connectors"),
    allowLowerCredentialLimits: lowerLevelAllowed(value, "credentials"),
    allowLowerProviderLimits: lowerLevelAllowed(value, "providers"),
    allowLowerPathLimits: lowerLevelAllowed(value, "external_paths"),
    allowLowerUrlLimits: lowerLevelAllowed(value, "external_urls")
  };
}

function credentialDraftFromCredential(credential: FileConnectorCredential, scopeType: ConnectorScope): ConnectorCredentialDraft {
  const policy = credential.policy_sources.find((source) => source.scope_type === credential.scope_type && source.scope_id === credential.scope_id)?.policy ??
  credential.policy_sources[0]?.policy ??
  {};
  const allowPaths = policyRuleList(policy, "allow", "external_paths");
  const denyPaths = policyRuleList(policy, "deny", "external_paths");
  const denyCredentials = policyRuleList(policy, "deny", "credentials");
  return {
    ...emptyCredentialDraft(scopeType),
    id: credential.id,
    label: credential.label,
    provider: credential.provider ? knownProvider(credential.provider) : "",
    enabled: credential.enabled,
    credentialMode: knownCredentialMode(credential.credential_mode),
    username: credential.username ?? "",
    policyMode: denyCredentials.includes("*") || denyCredentials.includes(credential.id) ? "deny-provider" : allowPaths.length > 0 ? "allowed-paths" : "allow-provider",
    allowedPaths: allowPaths.join("\n"),
    deniedPaths: denyPaths.join("\n"),
    metadataJson: JSON.stringify(credential.metadata ?? {}, null, 2)
  };
}

function policyFromDraft(draft: ConnectorProfileDraft): Record<string, unknown> {
  const denyPaths = lines(draft.deniedPaths);
  if (draft.policyMode === "deny-provider") {
    return {
      deny: {
        providers: [draft.provider],
        ...(denyPaths.length ? { external_paths: denyPaths } : {})
      }
    };
  }
  const allowPaths = draft.policyMode === "allowed-paths" ? lines(draft.allowedPaths) : [];
  return {
    allow: {
      providers: [draft.provider],
      ...(allowPaths.length ? { external_paths: allowPaths } : {})
    },
    ...(denyPaths.length ? { deny: { external_paths: denyPaths } } : {})
  };
}

function credentialPolicyFromDraft(draft: ConnectorCredentialDraft): Record<string, unknown> {
  const credentialId = draft.id.trim();
  const denyPaths = lines(draft.deniedPaths);
  const providerRules = draft.provider ? { providers: [draft.provider] } : {};
  if (draft.policyMode === "deny-provider") {
    return {
      deny: {
        credentials: credentialId ? [credentialId] : ["*"],
        ...providerRules,
        ...(denyPaths.length ? { external_paths: denyPaths } : {})
      }
    };
  }
  const allowPaths = draft.policyMode === "allowed-paths" ? lines(draft.allowedPaths) : [];
  return {
    allow: {
      ...(credentialId ? { credentials: [credentialId] } : {}),
      ...providerRules,
      ...(allowPaths.length ? { external_paths: allowPaths } : {})
    },
    ...(denyPaths.length ? { deny: { external_paths: denyPaths } } : {})
  };
}

function centralPolicyFromDraft(draft: CentralConnectorPolicyDraft): Record<string, unknown> {
  const allow = compactRuleGroup({
    connectors: lines(draft.allowedConnectors),
    credentials: lines(draft.allowedCredentials),
    providers: lines(draft.allowedProviders),
    external_paths: lines(draft.allowedPaths),
    external_urls: lines(draft.allowedUrls)
  });
  const deny = compactRuleGroup({
    connectors: lines(draft.deniedConnectors),
    credentials: lines(draft.deniedCredentials),
    providers: lines(draft.deniedProviders),
    external_paths: lines(draft.deniedPaths),
    external_urls: lines(draft.deniedUrls)
  });
  const lower: Record<string, boolean> = {};
  setLowerLimit(lower, "connectors", draft.allowLowerConnectionLimits);
  setLowerLimit(lower, "credentials", draft.allowLowerCredentialLimits);
  setLowerLimit(lower, "providers", draft.allowLowerProviderLimits);
  setLowerLimit(lower, "external_paths", draft.allowLowerPathLimits);
  setLowerLimit(lower, "external_urls", draft.allowLowerUrlLimits);
  return {
    ...(Object.keys(allow).length ? { allow } : {}),
    ...(Object.keys(deny).length ? { deny } : {}),
    ...(Object.keys(lower).length ? { allow_lower_level_limits: lower } : {})
  };
}

function metadataFromDraft(draft: ConnectorProfileDraft): Record<string, unknown> {
  const metadata = metadataFromJson(draft.metadataJson);
  if (draft.browseProtocol) metadata.browse_protocol = draft.browseProtocol;else
  delete metadata.browse_protocol;
  if (draft.provider === "smb") {
    metadata.require_signing = draft.requireSigning;
    metadata.auth_protocol = draft.authProtocol || "ntlm";
  }
  return metadata;
}

function metadataFromJson(value: string): Record<string, unknown> {
  const clean = value.trim();
  const parsed = clean ? JSON.parse(clean) : {};
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error("i18n:govoplan-files.metadata_json_must_be_an_object.48629f13");
  }
  return { ...parsed } as Record<string, unknown>;
}

function cleanOrNull(value: string): string | null {
  const clean = value.trim();
  return clean ? clean : null;
}

function cleanOrUndefined(value: string): string | undefined {
  const clean = value.trim();
  return clean ? clean : undefined;
}

function lines(value: string): string[] {
  return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
}

function policyRuleList(policy: Record<string, unknown>, kind: "allow" | "deny", field: "connectors" | "providers" | "credentials" | "external_paths" | "external_urls"): string[] {
  const rule = recordValue(policy[kind]) ??
  recordValue(policy[kind === "allow" ? "allowlist" : "denylist"]) ??
  recordValue(policy[kind === "allow" ? "whitelist" : "blacklist"]) ??
  {};
  if (field === "connectors") return stringList(rule.connectors ?? rule.connector_ids ?? rule.connector_id);
  if (field === "providers") return stringList(rule.providers ?? rule.provider);
  if (field === "credentials") return stringList(rule.credentials ?? rule.credential_ids ?? rule.credential_id);
  if (field === "external_urls") return stringList(rule.external_urls ?? rule.urls ?? rule.external_url);
  return stringList(rule.external_paths ?? rule.paths ?? rule.path_prefixes ?? rule.external_path);
}

function compactRuleGroup(value: Record<string, string[]>): Record<string, string[]> {
  return Object.fromEntries(Object.entries(value).filter(([, items]) => items.length > 0));
}

function setLowerLimit(target: Record<string, boolean>, field: string, allowed: boolean) {
  if (allowed) return;
  target[`allow.${field}`] = false;
  target[`deny.${field}`] = false;
}

function lowerLevelAllowed(policy: Record<string, unknown>, field: string): boolean {
  const lower = recordValue(policy.allow_lower_level_limits) ?? {};
  return lower[`allow.${field}`] !== false && lower[`deny.${field}`] !== false;
}

function recordValue(value: unknown): Record<string, unknown> | null {
  return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
}

function stringList(value: unknown): string[] {
  if (typeof value === "string") return value.trim() ? [value.trim()] : [];
  if (Array.isArray(value)) return value.map((item) => String(item).trim()).filter(Boolean);
  return [];
}

function knownProvider(value: string): Provider {
  const clean = value.trim().toLowerCase();
  return PROVIDERS.some((provider) => provider.id === clean) ? clean as Provider : "generic";
}

function knownCredentialMode(value: string): CredentialMode {
  if (["none", "anonymous", "basic", "token", "secret_ref"].includes(value)) return value as CredentialMode;
  return "none";
}

function sortConnectorProfiles(left: FileConnectorProfile, right: FileConnectorProfile): number {
  return scopeOrder(left.scope_type) - scopeOrder(right.scope_type) || left.label.localeCompare(right.label) || left.id.localeCompare(right.id);
}

function sortConnectorCredentials(left: FileConnectorCredential, right: FileConnectorCredential): number {
  return scopeOrder(left.scope_type) - scopeOrder(right.scope_type) || left.label.localeCompare(right.label) || left.id.localeCompare(right.id);
}

function scopeOrder(scopeType: FileConnectorScope): number {
  return { system: 0, tenant: 1, user: 2, group: 3, campaign: 4 }[scopeType] ?? 99;
}

function providerLabel(provider: string): string {
  return PROVIDERS.find((item) => item.id === provider)?.label ?? provider;
}

function errorMessage(err: unknown): string {
  return err instanceof Error ? err.message : String(err);
}
