import { useEffect, useMemo, useState } from "react";
import { Pencil } from "lucide-react";
import { useGuardedNavigate, usePlatformModuleInstalled, usePlatformUiCapability, type FilesFileExplorerUiCapability, type FilesFileSpace } from "@govoplan/core-webui";
import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { PageTitle } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui";
import LockedVersionNotice from "./components/LockedVersionNotice";
import VersionLine from "./components/VersionLine";
import { ToggleSwitch } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import DataGrid, { DataGridEmptyAction, DataGridRowActions, type DataGridColumn } from "../../components/table/DataGrid";
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
import { asArray, asRecord, isAuditLockedVersion } from "./utils/campaignView";
import { updateNested } from "./utils/draftEditor";
import { AttachmentRulesDataGrid } from "./components/AttachmentRulesOverlay";
import TemplateExpressionEditorDialog from "./components/TemplateExpressionEditorDialog";
import { countIndividualAttachmentRules, countIndividualAttachmentRulesForBasePath, createAttachmentBasePath, ensureAttachmentBasePaths, normalizeAttachmentBasePaths, normalizeAttachmentRules, normalizeAttachmentZipCollection, createAttachmentZipArchive, parseManagedAttachmentSource, removeIndividualAttachmentRulesForBasePath, summarizeAttachmentRules, type AttachmentBasePath, type AttachmentZipArchive, type AttachmentZipCollection } from "./utils/attachments";
import { insertAfter, moveArrayItem, i18nMessage } from "@govoplan/core-webui";
import { getDraftFields, humanizeFieldName } from "./utils/fieldDefinitions";
import { buildTemplatePreviewContext, recipientAddressTemplateFieldOptions } from "./utils/templatePlaceholders";

type PathChooserState = {index: number;};
type IndividualDisableState = {index: number;usageCount: number;};

export default function AttachmentsDataPage({ settings, campaignId }: {settings: ApiSettings;campaignId: string;}) {
  const navigate = useGuardedNavigate();
  const filesModuleInstalled = usePlatformModuleInstalled("files");
  const filesFileExplorer = usePlatformUiCapability<FilesFileExplorerUiCapability>("files.fileExplorer");
  const ManagedFileChooser = filesModuleInstalled ? filesFileExplorer?.ManagedFileChooser : null;
  const managedFilesAvailable = Boolean(ManagedFileChooser);
  const listManagedFileSpaces = filesModuleInstalled ? filesFileExplorer?.listFileSpaces : undefined;
  const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
  const [pathChooser, setPathChooser] = useState<PathChooserState | null>(null);
  const [fileSpaces, setFileSpaces] = useState<FilesFileSpace[]>([]);
  const [individualDisable, setIndividualDisable] = useState<IndividualDisableState | null>(null);
  const [zipNameEditorIndex, setZipNameEditorIndex] = useState<number | null>(null);
  const version = data.currentVersion;
  const locked = isAuditLockedVersion(version, data.campaign?.current_version_id);
  const { draft, setDraft, displayDraft, dirty, saveState, localError, patch, markDirty, saveDraft } = useCampaignDraftEditor({
    settings,
    campaignId,
    version,
    locked,
    reload,
    setError,
    currentStep: "files",
    unsavedTitle: "i18n:govoplan-campaign.unsaved_attachment_settings.a6045f67",
    unsavedMessage: "i18n:govoplan-campaign.attachment_settings_have_unsaved_changes_save_th.b9b415bb"
  });
  const attachments = asRecord(displayDraft.attachments);
  const basePaths = useMemo(() => normalizeAttachmentBasePaths(attachments.base_paths, attachments), [attachments]);
  const globalRules = useMemo(() => normalizeAttachmentRules(attachments.global), [attachments.global]);
  const zipConfig = useMemo(() => normalizeAttachmentZipCollection(attachments.zip), [attachments.zip]);
  const filenameFieldOptions = useMemo(() => buildZipFilenameFieldOptions(displayDraft), [displayDraft]);
  const passwordFields = useMemo(() => getDraftFields(displayDraft).filter((field) => field.type === "password"), [displayDraft]);
  const zipArchiveNameValidation = useMemo(
    () => zipConfig.enabled ? validateZipArchiveNames(zipConfig.archives) : EMPTY_ZIP_ARCHIVE_NAME_VALIDATION,
    [zipConfig.archives, zipConfig.enabled]
  );
  const canSave = dirty && !locked && Boolean(draft) && !zipArchiveNameValidation.message;
  const globalSummary = useMemo(() => summarizeAttachmentRules(globalRules), [globalRules]);
  const individualRulesCount = useMemo(() => countIndividualAttachmentRules(displayDraft.entries), [displayDraft.entries]);
  const attachmentPreviewEntry = useMemo(
    () => asArray(asRecord(displayDraft.entries).inline).map(asRecord).find((entry) => entry.active !== false) ?? {},
    [displayDraft.entries]
  );
  const attachmentPreviewContext = useMemo(
    () => buildTemplatePreviewContext(displayDraft, attachmentPreviewEntry),
    [attachmentPreviewEntry, displayDraft]
  );

  useEffect(() => {
    if (!listManagedFileSpaces) {
      setFileSpaces([]);
      return;
    }
    let cancelled = false;
    void listManagedFileSpaces(settings).
    then((response) => {if (!cancelled) setFileSpaces(response.spaces);}).
    catch(() => {if (!cancelled) setFileSpaces([]);});
    return () => {cancelled = true;};
  }, [listManagedFileSpaces, settings.apiBaseUrl, settings.apiKey, settings.accessToken]);

  function patchBasePaths(paths: AttachmentBasePath[]) {
    if (locked) return;
    const normalized = ensureAttachmentBasePaths(paths);
    setDraft((current) => {
      const withPaths = updateNested(current ?? {}, ["attachments", "base_paths"], normalized);
      return updateNested(withPaths, ["attachments", "base_path"], normalized[0]?.path || ".");
    });
    markDirty();
  }

  function patchBasePath(index: number, patch: Partial<AttachmentBasePath>) {
    patchBasePaths(basePaths.map((basePath, currentIndex) => currentIndex === index ? { ...basePath, ...patch } : basePath));
  }

  function addBasePath(afterIndex = basePaths.length - 1) {
    patchBasePaths(insertAfter(basePaths, afterIndex, createAttachmentBasePath("New attachment source", ".")));
  }

  function removeBasePath(index: number) {
    patchBasePaths(basePaths.filter((_, currentIndex) => currentIndex !== index));
  }

  function moveBasePath(index: number, targetIndex: number) {
    if (locked || index === targetIndex) return;
    patchBasePaths(moveArrayItem(basePaths, index, targetIndex));
  }

  function setIndividualEligibility(index: number, checked: boolean) {
    if (locked) return;
    if (checked) {
      patchBasePath(index, { allow_individual: true });
      return;
    }
    const basePath = basePaths[index];
    if (!basePath) return;
    const usageCount = countIndividualAttachmentRulesForBasePath(displayDraft.entries, basePath);
    if (usageCount > 0) {
      setIndividualDisable({ index, usageCount });
      return;
    }
    patchBasePath(index, { allow_individual: false });
  }

  function confirmIndividualDisable() {
    if (!individualDisable) return;
    const basePath = basePaths[individualDisable.index];
    if (!basePath) {
      setIndividualDisable(null);
      return;
    }
    const nextPaths = basePaths.map((item, index) => index === individualDisable.index ? { ...item, allow_individual: false } : item);
    setDraft((current) => {
      const source = current ?? {};
      const withPaths = updateNested(source, ["attachments", "base_paths"], nextPaths);
      const withPrimaryPath = updateNested(withPaths, ["attachments", "base_path"], nextPaths[0]?.path || ".");
      return updateNested(withPrimaryPath, ["entries"], removeIndividualAttachmentRulesForBasePath(asRecord(source).entries, basePath));
    });
    markDirty();
    setIndividualDisable(null);
  }



  function patchZipCollection(next: AttachmentZipCollection) {
    if (locked) return;
    patch(["attachments", "zip"], next);
  }

  function setZipEnabled(enabled: boolean) {
    const archives = enabled && zipConfig.archives.length === 0 ?
    [createAttachmentZipArchive("{{local:id}}-attachments.zip", true)] :
    zipConfig.archives;
    patchZipCollection({ enabled, archives });
  }

  function patchZipArchive(index: number, archivePatch: Partial<AttachmentZipArchive>) {
    const archives = zipConfig.archives.map((archive, currentIndex) => {
      if (currentIndex !== index) return archive;
      const next = { ...archive, ...archivePatch };
      if (archivePatch.password_field !== undefined || archivePatch.password_scope !== undefined) {
        next.password_mode = "field";
        delete next.password;
        delete next.password_template;
      }
      return next;
    });
    patchZipCollection({ ...zipConfig, archives });
  }

  function setStandardZipArchive(index: number) {
    patchZipCollection({
      ...zipConfig,
      archives: zipConfig.archives.map((archive, currentIndex) => ({ ...archive, standard: currentIndex === index }))
    });
  }

  function addZipArchive(afterIndex = zipConfig.archives.length - 1) {
    const archive = createAttachmentZipArchive(`attachments-${zipConfig.archives.length + 1}.zip`, zipConfig.archives.length === 0);
    patchZipCollection({ ...zipConfig, archives: insertAfter(zipConfig.archives, afterIndex, archive) });
  }

  function removeZipArchive(index: number) {
    const removed = zipConfig.archives[index];
    if (!removed) return;
    const removedWasStandard = removed.standard;
    const archives = zipConfig.archives.filter((_, currentIndex) => currentIndex !== index);
    if (removedWasStandard && archives.length > 0) archives[0] = { ...archives[0], standard: true };

    const resetRule = (value: unknown) => {
      const rule = asRecord(value);
      const ruleZip = asRecord(rule.zip);
      return String(ruleZip.archive_id ?? "") === removed.id ?
      { ...rule, zip: { ...ruleZip, archive_id: "inherit" } } :
      rule;
    };
    setDraft((current) => {
      const source = asRecord(current ?? {});
      const currentAttachments = asRecord(source.attachments);
      const currentEntries = asRecord(source.entries);
      return {
        ...source,
        attachments: {
          ...currentAttachments,
          zip: { ...zipConfig, archives },
          global: asArray(currentAttachments.global).map(resetRule)
        },
        entries: {
          ...currentEntries,
          inline: asArray(currentEntries.inline).map((value) => {
            const entry = asRecord(value);
            return { ...entry, attachments: asArray(entry.attachments).map(resetRule) };
          })
        }
      };
    });
    markDirty();
  }

  function moveZipArchive(index: number, targetIndex: number) {
    if (locked || index === targetIndex) return;
    patchZipCollection({ ...zipConfig, archives: moveArrayItem(zipConfig.archives, index, targetIndex) });
  }


  return (
    <div className="content-pad workspace-data-page">
      <div className="page-heading split workspace-heading">
        <div>
          <PageTitle loading={loading}>i18n:govoplan-campaign.attachments.6771ade6</PageTitle>
          <VersionLine version={version} versions={data.versions} status={saveState} />
        </div>
        <div className="button-row compact-actions">
          {filesModuleInstalled && <Button onClick={() => navigate("/files")}>i18n:govoplan-campaign.manage_files.90a419f7</Button>}
          <Button onClick={reload} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>
          <Button variant="primary" onClick={() => saveDraft("manual")} disabled={!canSave}>i18n:govoplan-campaign.save.efc007a3</Button>
        </div>
      </div>

      {error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
      {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
      {locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} currentVersionId={data.campaign?.current_version_id} reload={reload} message="i18n:govoplan-campaign.this_page_is_read_only_for_the_selected_version.dacf5743" />}

      <LoadingFrame loading={loading || !draft} label="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50">
        <>
          <Card title="i18n:govoplan-campaign.attachment_sources.8ef0a6ce">
            <div className="admin-table-surface attachment-sources-table-surface">
              <DataGrid
                id={`campaign-${campaignId}-attachment-sources`}
                rows={basePaths}
                columns={attachmentSourceColumns({ locked, basePaths, fileSpaces, managedFilesAvailable, patchBasePath, setIndividualEligibility, addBasePath, moveBasePath, removeBasePath, setPathChooser })}
                getRowKey={(basePath) => basePath.id}
                emptyText="i18n:govoplan-campaign.no_attachment_sources_configured.48664606"
                emptyAction={<DataGridEmptyAction onAdd={() => addBasePath(-1)} disabled={locked} label="i18n:govoplan-campaign.add_first_attachment_source.cefa7882" />}
                className="attachment-sources-table-wrap attachment-sources-table" />
              
            </div>
          </Card>

          <Card title="i18n:govoplan-campaign.zip_attachments.6b58ed68" collapsible>
            <div className="attachment-zip-master-toggle">
              <ToggleSwitch
                label="i18n:govoplan-campaign.enable_zip_attachments.6077075b"
                checked={zipConfig.enabled}
                disabled={locked}
                onChange={setZipEnabled} />
              
            </div>
            <div className="admin-table-surface attachment-zip-table-surface">
              <DataGrid
                id={`campaign-${campaignId}-zip-archives`}
                rows={zipConfig.archives}
                columns={zipArchiveColumns({
                  disabled: locked || !zipConfig.enabled,
                  archives: zipConfig.archives,
                  invalidNameIndexes: zipArchiveNameValidation.invalidIndexes,
                  onEditName: setZipNameEditorIndex,
                  passwordFields,
                  patchArchive: patchZipArchive,
                  setStandard: setStandardZipArchive,
                  addArchive: addZipArchive,
                  moveArchive: moveZipArchive,
                  removeArchive: removeZipArchive
                })}
                getRowKey={(archive) => archive.id}
                emptyText={zipConfig.enabled ? "i18n:govoplan-campaign.no_zip_attachments_configured.29c16424" : "i18n:govoplan-campaign.zip_attachments_are_disabled.6969b41d"}
                emptyAction={zipConfig.enabled ? <DataGridEmptyAction onAdd={() => addZipArchive(-1)} disabled={locked} label="i18n:govoplan-campaign.add_first_zip_attachment.f64cc332" /> : undefined}
                className="attachment-zip-table-wrap" />
              
            </div>
            {zipConfig.enabled && passwordFields.length === 0 && zipConfig.archives.some((archive) => archive.password_enabled) &&
            <DismissibleAlert tone="warning">i18n:govoplan-campaign.no_password_field_exists_add_one_under_fields_wi.2b6d4a7f <strong>i18n:govoplan-campaign.password.8be3c943</strong>.</DismissibleAlert>
            }
            {zipArchiveNameValidation.message &&
            <DismissibleAlert tone="danger" compact resetKey={zipArchiveNameValidation.message} className="attachment-zip-name-error">{zipArchiveNameValidation.message}</DismissibleAlert>
            }
            <p className="muted small-note">i18n:govoplan-campaign.archive_names_support_recipient_and_campaign_fie.d5b1b2d1</p>
          </Card>

          <Card title="i18n:govoplan-campaign.global_attachments.492bd841" collapsible>
            <AttachmentRulesDataGrid
              id={`campaign-${campaignId}-global-attachments`}
              rules={globalRules}
              disabled={locked}
              emptyText="i18n:govoplan-campaign.no_global_attachments_are_configured_yet_add_fil.d9fdfbb8"
              basePaths={basePaths}
              settings={settings}
              campaignId={campaignId}
              zipConfig={zipConfig}
              filesModuleInstalled={managedFilesAvailable}
              previewContext={attachmentPreviewContext}
              onChange={(rules) => patch(["attachments", "global"], rules)} />
            
          </Card>

          <Card title="i18n:govoplan-campaign.statistics.2086b21f" collapsible>
            <dl className="detail-list">
              <div><dt>i18n:govoplan-campaign.base_paths.edf35a97</dt><dd>{basePaths.length}</dd></div>
              <div><dt>i18n:govoplan-campaign.global_attachments.438263a1</dt><dd>direct: {globalSummary.direct} i18n:govoplan-campaign.rules.e5d36074 {globalSummary.rules}</dd></div>
              <div><dt>i18n:govoplan-campaign.per_recipient_patterns.53da30da</dt><dd>{individualRulesCount}</dd></div>
              <div><dt>i18n:govoplan-campaign.upload_support.1c54931c</dt><dd>{managedFilesAvailable ? "i18n:govoplan-campaign.connected_through_files.95007112" : "i18n:govoplan-campaign.manual_paths.8e20627a"}</dd></div>
            </dl>
            <p className="muted small-note">{managedFilesAvailable ?
              "i18n:govoplan-campaign.files_are_managed_in_the_top_level_files_module_.4b370222" :
              filesModuleInstalled ?
              "i18n:govoplan-campaign.managed_file_browsing_is_unavailable_use_manual_.782867fe" :
              "i18n:govoplan-campaign.the_files_module_is_not_installed_use_manual_pat.49abc725"}</p>
          </Card>

        </>
      </LoadingFrame>

      <TemplateExpressionEditorDialog
        open={zipNameEditorIndex !== null && Boolean(zipConfig.archives[zipNameEditorIndex])}
        title="i18n:govoplan-campaign.edit_zip_archive_filename.5a2c95e4"
        value={zipNameEditorIndex !== null ? zipConfig.archives[zipNameEditorIndex]?.name ?? "" : ""}
        localFields={filenameFieldOptions.filter((field) => field.namespace === "local").map(({ name, label }) => ({ name, label }))}
        globalFields={filenameFieldOptions.filter((field) => field.namespace === "global").map(({ name, label }) => ({ name, label }))}
        placeholder="attachments.zip"
        description="i18n:govoplan-campaign.use_a_fixed_filename_or_combine_text_with_recipi.43a091fd"
        saveLabel="i18n:govoplan-campaign.save_filename.2dbd2273"
        validate={(value) => zipNameEditorIndex === null ? "" : describeZipArchiveNameValueProblem(zipConfig.archives, zipNameEditorIndex, value)}
        onCancel={() => setZipNameEditorIndex(null)}
        onSave={(name) => {
          if (zipNameEditorIndex === null) return;
          patchZipArchive(zipNameEditorIndex, { name });
          setZipNameEditorIndex(null);
        }}
        onAddField={(name) => {
          if (!draft || locked || !name) return;
          const existingFields = asArray(draft.fields).map(asRecord);
          if (existingFields.some((field) => String(field.name || field.id || "").trim() === name)) return;
          patch(["fields"], [
          ...existingFields,
          { name, label: humanizeFieldName(name), type: "string", required: false, can_override: true }]
          );
        }}
        canAddField={(name) => !getDraftFields(displayDraft).some((field) => field.name === name)} />
      

      {pathChooser && ManagedFileChooser &&
      <ManagedFileChooser
        open
        settings={settings}
        storageScope={campaignId}
        mode="folder"
        source={basePaths[pathChooser.index]?.source}
        basePath={basePaths[pathChooser.index]?.path}
        onClose={() => setPathChooser(null)}
        onSelectFolder={(selection) => {
          const current = basePaths[pathChooser.index];
          const folderLabel = selection.folderPath ? selection.folderPath.split("/").filter(Boolean).slice(-1)[0] : selection.space.label;
          patchBasePath(pathChooser.index, {
            source: selection.source,
            path: selection.folderPath || ".",
            name: !current?.name || current.name === "i18n:govoplan-campaign.new_attachment_source.563145ba" || current.name === "i18n:govoplan-campaign.campaign_files.96e7004b" ?
            `${selection.space.label}${selection.folderPath ? ` / ${folderLabel}` : ""}` :
            current.name
          });
          setPathChooser(null);
        }} />

      }


      <ConfirmDialog
        open={Boolean(individualDisable)}
        title="i18n:govoplan-campaign.disable_individual_attachments_for_this_source.185f9a95"
        message={individualDisable ? i18nMessage("i18n:govoplan-campaign.value_individual_attachment_value_this_source_di.3c7428e7", { value0:
          individualDisable.usageCount, value1: individualDisable.usageCount === 1 ? "i18n:govoplan-campaign.entry_uses.5f269cab" : "i18n:govoplan-campaign.entries_use.e9169679" }) :
        ""}
        confirmLabel="i18n:govoplan-campaign.remove_individual_attachments.9e5f0ad3"
        cancelLabel="i18n:govoplan-campaign.cancel.77dfd213"
        tone="danger"
        onConfirm={confirmIndividualDisable}
        onCancel={() => setIndividualDisable(null)} />
      
    </div>);

}

type ZipArchiveColumnContext = {
  disabled: boolean;
  archives: AttachmentZipArchive[];
  invalidNameIndexes: ReadonlySet<number>;
  onEditName: (index: number) => void;
  passwordFields: ReturnType<typeof getDraftFields>;
  patchArchive: (index: number, patch: Partial<AttachmentZipArchive>) => void;
  setStandard: (index: number) => void;
  addArchive: (afterIndex?: number) => void;
  moveArchive: (index: number, targetIndex: number) => void;
  removeArchive: (index: number) => void;
};

function zipArchiveColumns({ disabled, archives, invalidNameIndexes, onEditName, passwordFields, patchArchive, setStandard, addArchive, moveArchive, removeArchive }: ZipArchiveColumnContext): DataGridColumn<AttachmentZipArchive>[] {
  return [
  {
    id: "name", header: "i18n:govoplan-campaign.archive_name.6310f9e1", width: "minmax(360px, 1fr)", resizable: true, sortable: true, filterable: true, sticky: "start",
    render: (archive, index) =>
    <button
      type="button"
      className="attachment-zip-name-button"
      disabled={disabled}
      aria-invalid={invalidNameIndexes.has(index) || undefined}
      aria-label={i18nMessage("i18n:govoplan-campaign.edit_zip_archive_filename_value.deb41dfc", { value0: archive.name || index + 1 })}
      title={invalidNameIndexes.has(index) ? "i18n:govoplan-campaign.archive_filenames_must_be_present_and_unique.aa2fb639" : "i18n:govoplan-campaign.edit_zip_archive_filename.5a2c95e4"}
      onClick={() => onEditName(index)}>
      
          <span className="attachment-zip-name-value">{archive.name || "i18n:govoplan-campaign.unnamed_zip_attachment.54f515bb"}</span>
          <Pencil size={15} aria-hidden="true" />
        </button>,

    value: (archive) => archive.name
  },
  {
    id: "standard", header: "i18n:govoplan-campaign.standard.2dfa6607", width: 150, sortable: true, filterable: true,
    columnType: "from-list", list: { options: [{ value: "standard", label: "i18n:govoplan-campaign.standard.2dfa6607" }, { value: "optional", label: "i18n:govoplan-campaign.optional.0c6c4102" }] },
    render: (archive, index) => <ToggleSwitch label="i18n:govoplan-campaign.standard.2dfa6607" checked={archive.standard} disabled={disabled} onChange={(checked) => checked && setStandard(index)} />,
    value: (archive) => archive.standard ? "standard" : "optional"
  },
  {
    id: "password_enabled", header: "i18n:govoplan-campaign.password.8be3c943", width: 165, sortable: true, filterable: true,
    columnType: "from-list", list: { options: [{ value: "protected", label: "i18n:govoplan-campaign.protected.28531336" }, { value: "none", label: "i18n:govoplan-campaign.no_password.18518368" }] },
    render: (archive, index) => <ToggleSwitch label="i18n:govoplan-campaign.protected.28531336" checked={archive.password_enabled} disabled={disabled} onChange={(checked) => patchArchive(index, { password_enabled: checked })} />,
    value: (archive) => archive.password_enabled ? "protected" : "none"
  },
  {
    id: "password_field", header: "i18n:govoplan-campaign.password_field.a1fc8a1c", width: 230, sortable: true, filterable: true,
    columnType: "from-list", list: { options: [{ value: "", label: "i18n:govoplan-campaign.no_field.1fe00ed4" }, ...passwordFields.map((field) => ({ value: field.name, label: field.label || field.name }))] },
    render: (archive, index) =>
    <select value={archive.password_field} disabled={disabled || !archive.password_enabled || passwordFields.length === 0} onChange={(event) => patchArchive(index, { password_field: event.target.value })}>
          <option value="">i18n:govoplan-campaign.select_password_field.4007aeb0</option>
          {passwordFields.map((field) => <option key={field.name} value={field.name}>{field.label || field.name}</option>)}
        </select>,

    value: (archive) => archive.password_field
  },
  {
    id: "password_scope", header: "i18n:govoplan-campaign.value_scope.5e1e16b2", width: 190, sortable: true, filterable: true,
    columnType: "from-list", list: { options: [{ value: "local", label: "i18n:govoplan-campaign.recipient_local.418a289f" }, { value: "global", label: "i18n:govoplan-campaign.campaign_global.ba944948" }] },
    render: (archive, index) =>
    <select value={archive.password_scope} disabled={disabled || !archive.password_enabled} onChange={(event) => patchArchive(index, { password_scope: event.target.value === "global" ? "global" : "local" })}>
          <option value="local">i18n:govoplan-campaign.recipient_local.418a289f</option>
          <option value="global">i18n:govoplan-campaign.campaign_global.ba944948</option>
        </select>,

    value: (archive) => archive.password_scope
  },
  {
    id: "actions", header: "i18n:govoplan-campaign.actions.c3cd636a", width: 180, sticky: "end",
    render: (_archive, index) => <DataGridRowActions
      disabled={disabled}
      onAddBelow={() => addArchive(index)}
      onRemove={() => removeArchive(index)}
      onMoveUp={index > 0 ? () => moveArchive(index, index - 1) : undefined}
      onMoveDown={index < archives.length - 1 ? () => moveArchive(index, index + 1) : undefined}
      addLabel="i18n:govoplan-campaign.add_zip_attachment_below.776dabc1"
      removeLabel="i18n:govoplan-campaign.remove_zip_attachment.d2bdf662"
      moveUpLabel="i18n:govoplan-campaign.move_zip_attachment_up.63446ac0"
      moveDownLabel="i18n:govoplan-campaign.move_zip_attachment_down.4ead4805" />

  }];

}

type ZipFilenameNamespace = "local" | "global";

type ZipFilenameFieldOption = {
  namespace: ZipFilenameNamespace;
  name: string;
  label: string;
};

type ZipArchiveNameValidation = {
  invalidIndexes: ReadonlySet<number>;
  message: string;
};

const EMPTY_ZIP_ARCHIVE_NAME_VALIDATION: ZipArchiveNameValidation = {
  invalidIndexes: new Set<number>(),
  message: ""
};

function buildZipFilenameFieldOptions(draft: Record<string, unknown>): ZipFilenameFieldOption[] {
  const fieldDefinitions = new Map(getDraftFields(draft).map((field) => [field.name, field]));
  const filenameFields = [...fieldDefinitions.values()].filter((field) => field.type !== "password");
  const labels = new Map(filenameFields.map((field) => [field.name, field.label || field.name]));
  const localNames = uniqueStrings(["id", "name", "email", ...recipientAddressTemplateFieldOptions().map((field) => field.name), ...filenameFields.map((field) => field.name)]);
  const globalNames = uniqueStrings([...filenameFields.map((field) => field.name), ...Object.keys(asRecord(draft.global_values))]).
  filter((name) => fieldDefinitions.get(name)?.type !== "password");
  return [
  ...localNames.map((name) => ({ namespace: "local" as const, name, label: labels.get(name) || humanizeFieldName(name) })),
  ...globalNames.map((name) => ({ namespace: "global" as const, name, label: labels.get(name) || humanizeFieldName(name) }))];

}

function describeZipArchiveNameValueProblem(archives: AttachmentZipArchive[], index: number, value: string): string {
  const normalized = normalizeZipArchiveName(value);
  if (!normalized) return "i18n:govoplan-campaign.archive_filename_must_not_be_empty.4234b817";
  const duplicate = archives.some((archive, currentIndex) => currentIndex !== index && normalizeZipArchiveName(archive.name) === normalized);
  return duplicate ? "i18n:govoplan-campaign.another_zip_attachment_already_uses_this_effecti.d05eb098" : "";
}

function validateZipArchiveNames(archives: AttachmentZipArchive[]): ZipArchiveNameValidation {
  const invalidIndexes = new Set<number>();
  const byName = new Map<string, number[]>();
  archives.forEach((archive, index) => {
    const normalized = normalizeZipArchiveName(archive.name);
    if (!normalized) {
      invalidIndexes.add(index);
      return;
    }
    const indexes = byName.get(normalized) ?? [];
    indexes.push(index);
    byName.set(normalized, indexes);
  });
  for (const indexes of byName.values()) {
    if (indexes.length < 2) continue;
    indexes.forEach((index) => invalidIndexes.add(index));
  }
  if (invalidIndexes.size === 0) return EMPTY_ZIP_ARCHIVE_NAME_VALIDATION;
  const hasEmpty = archives.some((archive, index) => invalidIndexes.has(index) && !archive.name.trim());
  const duplicateNames = [...byName.entries()].
  filter(([, indexes]) => indexes.length > 1).
  map(([, indexes]) => archives[indexes[0]]?.name.trim()).
  filter(Boolean);
  const parts: string[] = [];
  if (hasEmpty) parts.push("i18n:govoplan-campaign.archive_filenames_must_not_be_empty.42e91427");
  if (duplicateNames.length > 0) parts.push(`Archive filenames must be unique: ${duplicateNames.join(", ")}`);
  return { invalidIndexes, message: i18nMessage("i18n:govoplan-campaign.value_saving_is_disabled_until_this_is_corrected.cd0a2ca0", { value0: parts.join(". ") }) };
}

function normalizeZipArchiveName(value: string): string {
  const trimmed = value.trim().toLocaleLowerCase();
  if (!trimmed) return "";
  return trimmed.endsWith(".zip") ? trimmed : `${trimmed}.zip`;
}

function uniqueStrings(values: string[]): string[] {
  return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
}

type AttachmentSourceColumnContext = {
  locked: boolean;
  basePaths: AttachmentBasePath[];
  fileSpaces: FilesFileSpace[];
  managedFilesAvailable: boolean;
  patchBasePath: (index: number, patch: Partial<AttachmentBasePath>) => void;
  setIndividualEligibility: (index: number, checked: boolean) => void;
  addBasePath: (afterIndex?: number) => void;
  moveBasePath: (index: number, targetIndex: number) => void;
  removeBasePath: (index: number) => void;
  setPathChooser: (state: PathChooserState | null) => void;
};

function attachmentSourceColumns({ locked, basePaths, fileSpaces, managedFilesAvailable, patchBasePath, setIndividualEligibility, addBasePath, moveBasePath, removeBasePath, setPathChooser }: AttachmentSourceColumnContext): DataGridColumn<AttachmentBasePath>[] {
  return [
  { id: "name", header: "i18n:govoplan-campaign.name.709a2322", width: 220, resizable: true, sortable: true, filterable: true, sticky: "start", render: (basePath, index) => <input value={basePath.name} disabled={locked} placeholder="i18n:govoplan-campaign.campaign_files.96e7004b" onChange={(event) => patchBasePath(index, { name: event.target.value })} />, value: (basePath) => basePath.name },
  {
    id: "path",
    header: "i18n:govoplan-campaign.path.519e3913",
    width: "minmax(260px, 1fr)",
    resizable: true,
    sortable: true,
    filterable: true,
    render: (basePath, index) =>
    <div className="field-with-action split-field-action">
          <input
        className="chooser-display-input"
        value={managedFilesAvailable ? formatAttachmentSourcePath(basePath, fileSpaces) : basePath.path}
        disabled={locked}
        readOnly={managedFilesAvailable}
        tabIndex={managedFilesAvailable ? -1 : undefined}
        placeholder="i18n:govoplan-campaign.attachments_placeholder"
        onChange={(event) => {
          if (!managedFilesAvailable) patchBasePath(index, { path: event.target.value, source: "" });
        }}
        onClick={() => managedFilesAvailable && !locked && setPathChooser({ index })}
        onKeyDown={(event) => {
          if (managedFilesAvailable && !locked && (event.key === "Enter" || event.key === " ")) {
            event.preventDefault();
            setPathChooser({ index });
          }
        }} />
      
          {managedFilesAvailable && <Button onClick={() => setPathChooser({ index })} disabled={locked}>i18n:govoplan-campaign.choose_folder.1838a415</Button>}
        </div>,

    value: (basePath) => formatAttachmentSourcePath(basePath, fileSpaces)
  },
  { id: "individual", header: "i18n:govoplan-campaign.individual_attachments.8164fc7a", width: 260, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "individual", label: "i18n:govoplan-campaign.individual.a7abed83" }, { value: "global_only", label: "i18n:govoplan-campaign.global_only.4641751c" }] }, render: (basePath, index) => <ToggleSwitch label="i18n:govoplan-campaign.individual.a7abed83" checked={Boolean(basePath.allow_individual)} disabled={locked} onChange={(checked) => setIndividualEligibility(index, checked)} />, value: (basePath) => basePath.allow_individual ? "individual" : "global_only" },
  { id: "unsent_warning", header: "i18n:govoplan-campaign.unsent_warning.11bdfbf7", width: 200, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "warn", label: "i18n:govoplan-campaign.warn.3009d557" }, { value: "off", label: "i18n:govoplan-campaign.off.e3de5ab0" }] }, render: (basePath, index) => <ToggleSwitch label="i18n:govoplan-campaign.unsent.0d09fa0d" checked={Boolean(basePath.unsent_warning)} disabled={locked} onChange={(checked) => patchBasePath(index, { unsent_warning: checked })} />, value: (basePath) => basePath.unsent_warning ? "warn" : "off" },
  {
    id: "actions",
    header: "i18n:govoplan-campaign.actions.c3cd636a",
    width: 180,
    sticky: "end",
    render: (_basePath, index) =>
    <DataGridRowActions
      disabled={locked}
      removeDisabled={basePaths.length <= 1}
      onAddBelow={() => addBasePath(index)}
      onRemove={() => removeBasePath(index)}
      onMoveUp={index > 0 ? () => moveBasePath(index, index - 1) : undefined}
      onMoveDown={index < basePaths.length - 1 ? () => moveBasePath(index, index + 1) : undefined}
      addLabel="i18n:govoplan-campaign.add_attachment_source_below.635c6a61"
      removeLabel="i18n:govoplan-campaign.remove_attachment_source.95855c67"
      moveUpLabel="i18n:govoplan-campaign.move_attachment_source_up.956ec756"
      moveDownLabel="i18n:govoplan-campaign.move_attachment_source_down.cbf9ebd8" />


  }];

}
function formatAttachmentSourcePath(basePath: AttachmentBasePath, spaces: FilesFileSpace[]): string {
  const parsedSource = parseManagedAttachmentSource(basePath.source);
  const matchingSpace = parsedSource ?
  spaces.find((space) => space.owner_type === parsedSource.ownerType && space.owner_id === parsedSource.ownerId) :
  undefined;
  const rootLabel = matchingSpace?.label || (
  parsedSource?.ownerType === "user" ? "i18n:govoplan-campaign.my_files.71d01a41" : parsedSource?.ownerType === "group" ? "i18n:govoplan-campaign.group_files.1aacad0b" : "");
  const relativePath = basePath.path.trim().replace(/^\.\/?$/, "").replace(/^\/+|\/+$/g, "");
  if (rootLabel) return `${rootLabel}/${relativePath ? `${relativePath}/` : ""}`;
  return `${relativePath || "."}/`;
}
