import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { ArrowDown, ArrowUp, ArrowUpDown, File, Folder, FolderOpen, Home, Link2, Search } from "lucide-react";
import {
  listFileSpaces,
  listManagedFileSnapshot,
  resolveFilePatterns,
  shareFilesWithTarget,
  type FileFolder,
  type FileSpace,
  type ManagedFile } from
"../../../api/files";
import {
  Button,
  ConfirmDialog,
  Dialog,
  DismissibleAlert,
  LoadingFrame,
  type FilesManagedAttachmentSelection,
  type FilesManagedFileChooserProps,
  type FilesManagedFileLinkTarget,
  type FilesManagedFolderSelection, i18nMessage } from
"@govoplan/core-webui";
import { useFileTreeState } from "../hooks/useFileTreeState";
import type { FolderNode, SortColumn, SortDirection } from "../types";
import {
  buildExplorerEntries,
  buildFolderTree,
  formatBytes,
  formatDate,
  normalizeFilePath,
  normalizeFolder,
  parentFolderPath,
  sortExplorerEntries,
  treeNodeKey } from
"../utils/fileManagerUtils";

type ManagedFileChooserMode = FilesManagedFileChooserProps["mode"];

const DEFAULT_CHOOSER_FILE_ROW_HEIGHT = 52;
const DEFAULT_PATTERN_RESULT_ROW_HEIGHT = 58;
const DEFAULT_CHOOSER_TREE_ROW_HEIGHT = 32;
const CHOOSER_FILE_ROW_GAP = 4;
const CHOOSER_FILE_OVERSCAN_ROWS = 8;

type RememberedChooserState = {
  spaceId?: string;
  folder?: string;
  pattern?: string;
};

type ManagedFileSource = {
  ownerType: "user" | "group";
  ownerId: string;
};

const MANAGED_FILE_SOURCE_PREFIX = "managed:";

type ChooserExplorerEntry = ReturnType<typeof buildExplorerEntries>[number];
type VirtualFolderTreeRow = {node: FolderNode;depth: number;};

export type ManagedFolderSelection = FilesManagedFolderSelection;
export type ManagedAttachmentSelection = FilesManagedAttachmentSelection;

export default function ManagedFileChooser({
  open,
  settings,
  mode,
  storageScope,
  source,
  basePath = ".",
  initialPattern = "",
  rememberKey = mode,
  previewContext,
  linkTarget,
  renderPatternPreview,
  onClose,
  onSelectFolder,
  onSelectAttachment
}: FilesManagedFileChooserProps) {
  const parsedSource = useMemo(() => parseManagedFileSource(source), [source]);
  const normalizedBasePath = normalizeManagedBasePath(basePath);
  const storageKey = useMemo(
    () => `govoplan.files.managedFileChooser.${storageScope || linkTarget?.id || "global"}.${rememberKey}`,
    [linkTarget?.id, rememberKey, storageScope]
  );
  const remembered = useMemo(() => readRememberedState(storageKey), [storageKey, open]);
  const [spaces, setSpaces] = useState<FileSpace[]>([]);
  const [selectedSpaceId, setSelectedSpaceId] = useState("");
  const [files, setFiles] = useState<ManagedFile[]>([]);
  const [folders, setFolders] = useState<FileFolder[]>([]);
  const [currentFolder, setCurrentFolder] = useState("");
  const [pattern, setPattern] = useState("");
  const patternRef = useRef("");
  const rememberDraftTimerRef = useRef<number | null>(null);
  const [patternHasText, setPatternHasText] = useState(false);
  const [exactSelectionPattern, setExactSelectionPattern] = useState("");
  const [patternMatches, setPatternMatches] = useState<ManagedFile[] | null>(null);
  const [pendingExactFile, setPendingExactFile] = useState<ManagedFile | null>(null);
  const [loading, setLoading] = useState(false);
  const [resolving, setResolving] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");

  const selectedSpace = spaces.find((item) => item.id === selectedSpaceId) ?? null;
  const sourceLocked = mode === "attachment" && Boolean(parsedSource);
  const sourceMatchesSpace = selectedSpace ?
  mode === "folder" || !parsedSource || selectedSpace.owner_type === parsedSource.ownerType && selectedSpace.owner_id === parsedSource.ownerId :
  false;
  const effectiveRoot = mode === "attachment" ? normalizedBasePath : "";
  const entries = useMemo(
    () => buildExplorerEntries(files, folders, currentFolder, false),
    [currentFolder, files, folders]
  );
  const [sortColumn, setSortColumn] = useState<SortColumn>("name");
  const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
  const sortedEntries = useMemo(
    () => sortExplorerEntries(entries, sortColumn, sortDirection),
    [entries, sortColumn, sortDirection]
  );
  const breadcrumbs = chooserBreadcrumbs(currentFolder, effectiveRoot);
  const allTreeNodes = useMemo(() => buildFolderTree(files, folders), [files, folders]);
  const treeNodes = useMemo(() => nodesWithinRoot(allTreeNodes, effectiveRoot), [allTreeNodes, effectiveRoot]);
  const openFolder = useCallback((path: string) => {
    const normalized = normalizeFolder(path);
    if (mode === "attachment" && effectiveRoot && !isWithinRoot(normalized, effectiveRoot)) return;
    setCurrentFolder(normalized);
    setPatternMatches(null);
  }, [effectiveRoot, mode]);
  const { expandedTreeNodes, toggleTreeFolder } = useFileTreeState({
    activeSpaceId: selectedSpaceId,
    currentFolder,
    onOpenFolder: (_spaceId, path) => openFolder(path)
  });

  const toggleSort = useCallback((column: SortColumn) => {
    if (sortColumn === column) {
      setSortDirection((current) => current === "asc" ? "desc" : "asc");
      return;
    }
    setSortColumn(column);
    setSortDirection(column === "name" ? "asc" : "desc");
  }, [sortColumn]);

  const applyExactFile = useCallback((file: ManagedFile) => {
    const exactPattern = relativePath(file.display_path, effectiveRoot);
    patternRef.current = exactPattern;
    setPattern(exactPattern);
    setPatternHasText(true);
    setExactSelectionPattern(exactPattern);
    setPatternMatches([file]);
    setPendingExactFile(null);
  }, [effectiveRoot]);

  const scheduleRememberedState = useCallback((patternValue = patternRef.current) => {
    if (!open || !selectedSpaceId) return;
    if (rememberDraftTimerRef.current !== null) window.clearTimeout(rememberDraftTimerRef.current);
    rememberDraftTimerRef.current = window.setTimeout(() => {
      writeRememberedState(storageKey, { spaceId: selectedSpaceId, folder: currentFolder, pattern: patternValue });
      rememberDraftTimerRef.current = null;
    }, 350);
  }, [currentFolder, open, selectedSpaceId, storageKey]);

  useEffect(() => () => {
    if (rememberDraftTimerRef.current !== null) window.clearTimeout(rememberDraftTimerRef.current);
  }, []);

  const requestExactFile = useCallback((file: ManagedFile) => {
    const exactPattern = relativePath(file.display_path, effectiveRoot);
    const currentPattern = patternRef.current.trim();
    if (currentPattern && currentPattern !== exactPattern) {
      setPendingExactFile(file);
      return;
    }
    applyExactFile(file);
  }, [applyExactFile, effectiveRoot]);

  useEffect(() => {
    if (!open) return;
    let cancelled = false;
    setLoading(true);
    setError("");
    const initialPatternValue = remembered.pattern ?? initialPattern.trim();
    patternRef.current = initialPatternValue;
    setPattern(initialPatternValue);
    setPatternHasText(Boolean(initialPatternValue.trim()));
    setExactSelectionPattern("");
    setPatternMatches(null);
    setPendingExactFile(null);
    void listFileSpaces(settings).
    then((response) => {
      if (cancelled) return;
      const managedSpaces = response.spaces.filter((space) => space.space_type !== "connector");
      setSpaces(managedSpaces);
      const sourceSpace = managedSpaces.find((item) => sourceMatches(item, parsedSource));
      const rememberedSpace = managedSpaces.find((item) => item.id === remembered.spaceId);
      const firstSpace = sourceSpace ?? rememberedSpace ?? managedSpaces[0] ?? null;
      setSelectedSpaceId(firstSpace?.id ?? "");
    }).
    catch((reason: unknown) => {
      if (!cancelled) setError(errorMessage(reason));
    }).
    finally(() => {
      if (!cancelled) setLoading(false);
    });
    return () => {cancelled = true;};
  }, [initialPattern, open, parsedSource?.ownerId, parsedSource?.ownerType, settings.apiBaseUrl, settings.apiKey, settings.accessToken, storageKey]);

  useEffect(() => {
    if (!open || !selectedSpace) return;
    let cancelled = false;
    setLoading(true);
    setError("");
    setPatternMatches(null);
    const rememberedFolder = selectedSpace.id === remembered.spaceId ? normalizeFolder(remembered.folder || "") : "";
    const initialFolder = rememberedFolder && isWithinRoot(rememberedFolder, effectiveRoot) ? rememberedFolder : effectiveRoot;
    setCurrentFolder(initialFolder || effectiveRoot);
    void listManagedFileSnapshot(settings, {
      owner_type: selectedSpace.owner_type,
      owner_id: selectedSpace.owner_id,
      path_prefix: mode === "attachment" ? effectiveRoot || undefined : undefined
    }).
    then((response) => {
      if (cancelled) return;
      setFiles(response.files);
      setFolders(response.folders);
    }).
    catch((reason: unknown) => {
      if (!cancelled) setError(errorMessage(reason));
    }).
    finally(() => {
      if (!cancelled) setLoading(false);
    });
    return () => {cancelled = true;};
  }, [effectiveRoot, mode, open, selectedSpace?.id, settings.apiBaseUrl, settings.apiKey, settings.accessToken, storageKey]);

  useEffect(() => {
    scheduleRememberedState();
  }, [currentFolder, pattern, scheduleRememberedState]);

  function updatePatternInput(value: string) {
    patternRef.current = value;
    setPatternHasText((current) => {
      const next = Boolean(value.trim());
      return current === next ? current : next;
    });
    setExactSelectionPattern("");
    if (patternMatches !== null) setPatternMatches(null);
    scheduleRememberedState(value);
  }

  async function previewPattern(): Promise<ManagedFile[]> {
    if (!selectedSpace) return [];
    const trimmed = patternRef.current.trim();
    if (!trimmed) {
      setPatternMatches([]);
      setPattern("");
      setPatternHasText(false);
      return [];
    }
    const previewPatternValue = renderChooserPattern(trimmed, previewContext, renderPatternPreview);
    if (!previewPatternValue) {
      setPatternMatches([]);
      setPattern(trimmed);
      return [];
    }
    setPattern(trimmed);
    setPatternHasText(true);
    setResolving(true);
    setError("");
    try {
      const response = await resolveFilePatterns(settings, {
        patterns: [previewPatternValue],
        owner_type: selectedSpace.owner_type,
        owner_id: selectedSpace.owner_id,
        path_prefix: effectiveRoot || undefined,
        include_unmatched: false
      });
      const matches = response.patterns[0]?.matches ?? [];
      setPatternMatches(matches);
      return matches;
    } catch (reason) {
      setError(errorMessage(reason));
      return [];
    } finally {
      setResolving(false);
    }
  }

  async function shareMatches(matches: ManagedFile[]) {
    if (!linkTarget) return;
    const fileIds = matches.
    filter((file) => !isLinkedToTarget(file, linkTarget)).
    map((file) => file.id);
    const uniqueIds = [...new Set(fileIds)];
    if (uniqueIds.length > 0) await shareFilesWithTarget(settings, uniqueIds, linkTarget);
  }

  async function confirmSelection() {
    if (!selectedSpace || !sourceMatchesSpace) return;
    setSubmitting(true);
    setError("");
    try {
      if (mode === "folder") {
        onSelectFolder?.({
          space: selectedSpace,
          folderPath: currentFolder,
          source: encodeManagedFileSource(selectedSpace)
        });
        return;
      }

      const matches = patternMatches ?? (await previewPattern());
      await shareMatches(matches);
      const trimmedPattern = patternRef.current.trim();
      onSelectAttachment?.({
        space: selectedSpace,
        folderPath: effectiveRoot,
        source: encodeManagedFileSource(selectedSpace),
        fileFilter: trimmedPattern,
        matchCount: matches.length,
        fileIds: matches.map((file) => file.id),
        selectionType: isExactPattern(trimmedPattern) ? "file" : "pattern"
      });
    } catch (reason) {
      setError(errorMessage(reason));
    } finally {
      setSubmitting(false);
    }
  }

  if (!open || typeof document === "undefined") return null;

  const dialog =
  <>
      <Dialog
      open
      title={mode === "folder" ? "i18n:govoplan-files.choose_managed_attachment_source.b3709493" : "i18n:govoplan-files.choose_managed_file_or_pattern.bcf8e183"}
      onClose={onClose}
      closeDisabled={submitting}
      backdropClassName="managed-file-chooser-backdrop"
      className="managed-file-chooser-dialog"
      bodyClassName="managed-file-chooser-body"
      footerClassName="managed-file-chooser-footer"
      footer={
      <>
            <Button onClick={onClose} disabled={submitting}>i18n:govoplan-files.cancel.77dfd213</Button>
            <Button
          variant="primary"
          onClick={() => void confirmSelection()}
          disabled={loading || submitting || !selectedSpace || !sourceMatchesSpace || mode === "attachment" && (!parsedSource || !patternHasText)}>
          
              {submitting ? linkTarget ? "i18n:govoplan-files.linking.6f640897" : "i18n:govoplan-files.saving.56a2285c" : mode === "folder" ? "i18n:govoplan-files.use_this_folder.0e77d6d1" : "i18n:govoplan-files.use_pattern.ce54ba3e"}
            </Button>
          </>
      }>
      
        {error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
        {mode === "attachment" && !parsedSource &&
      <DismissibleAlert tone="warning" dismissible={false}>
            i18n:govoplan-files.this_attachment_base_path_is_not_connected_to_a_.3962b48f <strong>i18n:govoplan-files.attachments_attachment_sources.87d92d5b</strong> i18n:govoplan-files.first.49ec0838
          </DismissibleAlert>
      }
        {mode === "attachment" && parsedSource && !sourceMatchesSpace && !loading &&
      <DismissibleAlert tone="danger" dismissible={false}>i18n:govoplan-files.the_configured_managed_file_space_is_no_longer_a.5e93b729</DismissibleAlert>
      }

        <LoadingFrame loading={loading} label="i18n:govoplan-files.loading_connector_files.e5b0871d" className="managed-file-chooser-loading-frame">
        <div className="managed-file-chooser-layout" aria-busy={loading}>
          <aside className="managed-file-chooser-spaces file-tree-panel" aria-label="i18n:govoplan-files.file_spaces_and_folders.443d2056">
            <div className="file-tree-heading">i18n:govoplan-files.spaces.9b584b52</div>
            <div className="file-tree-list">
              {spaces.map((space) => {
              const selected = space.id === selectedSpaceId;
              const lockedOut = sourceLocked && !sourceMatches(space, parsedSource);
              return (
                <div className={`file-tree-space ${selected ? "is-active" : ""}`} key={space.id}>
                    <button
                    type="button"
                    className={`file-tree-node file-tree-root ${selected && currentFolder === effectiveRoot ? "is-active" : ""}`}
                    onClick={() => {
                      if (!selected) setSelectedSpaceId(space.id);else
                      openFolder(effectiveRoot);
                    }}
                    disabled={loading || lockedOut}>
                    
                      {space.owner_type === "group" ? <FolderOpen size={15} aria-hidden="true" /> : <Home size={15} aria-hidden="true" />}
                      <span>{space.label}</span>
                    </button>
                    {selected &&
                  <VirtualChooserFolderTree
                    nodes={treeNodes}
                    activeSpaceId={selectedSpaceId}
                    spaceId={space.id}
                    currentFolder={currentFolder}
                    expandedKeys={expandedTreeNodes}
                    onOpen={(_spaceId, path) => openFolder(path)}
                    onToggle={toggleTreeFolder}
                    disabled={loading} />

                  }
                  </div>);

            })}
              {!loading && spaces.length === 0 && <p className="muted small-note">i18n:govoplan-files.no_accessible_file_spaces_were_found.abcf5003</p>}
            </div>
          </aside>

          <section className={`managed-file-chooser-browser ${mode === "folder" ? "folder-mode" : "attachment-mode"}`}>
            <div className="managed-file-chooser-toolbar">
              <div className="managed-file-breadcrumb" aria-label="i18n:govoplan-files.current_folder.5aeab2f0">
                <button type="button" onClick={() => openFolder(effectiveRoot)} disabled={loading}>
                  <Home size={14} aria-hidden="true" /> {selectedSpace?.label || "i18n:govoplan-files.files.6ce6c512"}
                </button>
                {breadcrumbs.map((crumb) =>
              <span key={crumb.path}>
                    <span aria-hidden="true">/</span>
                    <button type="button" onClick={() => openFolder(crumb.path)} disabled={loading}>{crumb.name}</button>
                  </span>
              )}
              </div>
            </div>

            {mode === "attachment" &&
          <ManagedPatternEditor
            value={pattern}
            effectiveRoot={effectiveRoot}
            resolving={resolving}
            matchCount={patternMatches?.length ?? null}
            previewContext={previewContext}
            renderPatternPreview={renderPatternPreview}
            onDraftChange={updatePatternInput}
            onPreview={() => void previewPattern()}
            onBackToFolder={() => setPatternMatches(null)} />

          }

            {patternMatches !== null && mode === "attachment" ?
          <PatternResultList files={patternMatches} linkTarget={linkTarget} onChoose={requestExactFile} /> :

          <ChooserFileBrowser
            mode={mode}
            loading={loading}
            currentFolder={currentFolder}
            effectiveRoot={effectiveRoot}
            sortedEntries={sortedEntries}
            sortColumn={sortColumn}
            sortDirection={sortDirection}
            linkTarget={linkTarget}
            selectedExactPattern={exactSelectionPattern}
            onOpenFolder={openFolder}
            onToggleSort={toggleSort}
            onRequestExactFile={requestExactFile} />

          }

            {mode === "folder" &&
          <p className="muted small-note managed-file-chooser-note">i18n:govoplan-files.choose_the_folder_that_should_act_as_this_campai.9d0b7ef7</p>
          }
            {mode === "attachment" &&
          <p className="muted small-note managed-file-chooser-note">i18n:govoplan-files.clicking_a_file_sets_its_exact_relative_path_as_.590abd24</p>
          }
          </section>
        </div>
        </LoadingFrame>
      </Dialog>

      <ConfirmDialog
      open={Boolean(pendingExactFile)}
      title="i18n:govoplan-files.replace_the_current_pattern.96f80707"
      message={pendingExactFile ? i18nMessage("i18n:govoplan-files.replace_value_with_the_exact_file_value.4a63236f", { value0: patternRef.current.trim(), value1: relativePath(pendingExactFile.display_path, effectiveRoot) }) : "i18n:govoplan-files.replace_the_current_pattern.96f80707"}
      confirmLabel="i18n:govoplan-files.replace_pattern.d98e4c92"
      onConfirm={() => pendingExactFile && applyExactFile(pendingExactFile)}
      onCancel={() => setPendingExactFile(null)} />
    
    </>;


  return createPortal(dialog, document.body);
}

function ChooserSortButton({
  column,
  label,
  activeColumn,
  direction,
  onSort






}: {column: SortColumn;label: string;activeColumn: SortColumn;direction: SortDirection;onSort: (column: SortColumn) => void;}) {
  const active = activeColumn === column;
  const Icon = active ? direction === "asc" ? ArrowUp : ArrowDown : ArrowUpDown;
  return (
    <button
      type="button"
      className={active ? "is-sorted" : ""}
      aria-label={i18nMessage("i18n:govoplan-files.value_value_activate_to_sort.3953ba71", { value0: label, value1: active ? `sorted ${direction === "asc" ? "ascending" : "descending"}` : "i18n:govoplan-files.not_sorted.0abc03c5" })}
      onClick={() => onSort(column)}>
      
      <span>{label}</span><Icon size={14} aria-hidden="true" />
    </button>);

}

function ManagedPatternEditor({
  value,
  effectiveRoot,
  resolving,
  matchCount,
  previewContext,
  renderPatternPreview,
  onDraftChange,
  onPreview,
  onBackToFolder










}: {value: string;effectiveRoot: string;resolving: boolean;matchCount: number | null;previewContext?: Record<string, string>;renderPatternPreview?: FilesManagedFileChooserProps["renderPatternPreview"];onDraftChange: (value: string) => void;onPreview: () => void;onBackToFolder: () => void;}) {
  const [draft, setDraft] = useState(value);
  const renderedPattern = useMemo(() => renderChooserPattern(draft, previewContext, renderPatternPreview), [draft, previewContext, renderPatternPreview]);
  const patternIsRendered = Boolean(draft.trim() && renderedPattern && renderedPattern !== draft.trim());

  useEffect(() => {
    setDraft(value);
  }, [value]);

  function updateDraft(next: string) {
    setDraft(next);
    onDraftChange(next);
  }

  return (
    <div className="managed-pattern-editor">
      <label>
        <span>i18n:govoplan-files.file_or_pattern_relative_to.0ab4d831 <code>{effectiveRoot || "."}</code></span>
        <div className="field-with-action split-field-action">
          <input
            value={draft}
            onChange={(event) => updateDraft(event.target.value)}
            onKeyDown={(event) => {if (event.key === "Enter") onPreview();}}
            placeholder="folder/file.pdf or **/*.pdf" />
          
          <Button onClick={onPreview} disabled={resolving || !draft.trim()}>
            <Search size={15} aria-hidden="true" /> {resolving ? "i18n:govoplan-files.checking.494d0f68" : "i18n:govoplan-files.preview.f1fbb2b4"}
          </Button>
        </div>
      </label>
      {matchCount !== null &&
      <div className="managed-pattern-summary">
          <span>{matchCount} i18n:govoplan-files.current_file.1fbfcf3d{matchCount === 1 ? "" : "s"} i18n:govoplan-files.match.c79af5c2</span>
          <Button variant="ghost" onClick={onBackToFolder}>i18n:govoplan-files.back_to_folder.34ba1ed1</Button>
        </div>
      }
      {patternIsRendered && <small className="managed-pattern-rendered">i18n:govoplan-files.preview_resolves_to.a8d99432 <code>{renderedPattern}</code>.</small>}
    </div>);

}

function VirtualChooserFolderTree({
  nodes,
  activeSpaceId,
  spaceId,
  currentFolder,
  expandedKeys,
  onOpen,
  onToggle,
  disabled









}: {nodes: FolderNode[];activeSpaceId: string;spaceId: string;currentFolder: string;expandedKeys: Set<string>;onOpen: (spaceId: string, path: string) => void;onToggle: (spaceId: string, path: string) => void;disabled?: boolean;}) {
  const rows = useMemo(() => flattenVisibleFolderTree(nodes, spaceId, expandedKeys), [expandedKeys, nodes, spaceId]);
  const resetKey = useMemo(
    () => `${spaceId}:${currentFolder}:${rows.length}:${Array.from(expandedKeys).sort().join("|")}`,
    [currentFolder, expandedKeys, rows.length, spaceId]
  );
  const virtualRows = useFixedVirtualRows<HTMLDivElement>({
    itemCount: rows.length,
    resetKey,
    defaultRowHeight: DEFAULT_CHOOSER_TREE_ROW_HEIGHT
  });

  if (rows.length === 0) return null;

  return (
    <div
      className="managed-file-tree-virtual-list"
      ref={virtualRows.viewportRef}
      role="tree"
      aria-label="i18n:govoplan-files.folders.19adc47b"
      onScroll={(event) => virtualRows.setScrollTop(event.currentTarget.scrollTop)}>
      
      {virtualRows.topSpacerHeight > 0 &&
      <div className="managed-file-virtual-spacer" style={{ height: virtualRows.topSpacerHeight }} aria-hidden="true" />
      }
      {rows.slice(virtualRows.startIndex, virtualRows.endIndex).map(({ node, depth }, index) => {
        const nodeId = treeNodeKey(spaceId, node.path);
        const hasChildren = node.children.length > 0;
        const expanded = expandedKeys.has(nodeId);
        const active = activeSpaceId === spaceId && treeNodeKey(spaceId, currentFolder) === nodeId;
        return (
          <div
            key={nodeId}
            ref={index === 0 ? virtualRows.measureRowRef : undefined}
            className={`file-tree-node-wrap ${active ? "is-active" : ""}`}
            style={{ paddingLeft: `${Math.min(depth * 14, 56)}px` }}
            role="treeitem"
            aria-expanded={hasChildren ? expanded : undefined}
            aria-selected={active}>
            
            <button
              type="button"
              className="file-tree-toggle"
              onClick={(event) => {
                event.stopPropagation();
                if (hasChildren) onToggle(spaceId, node.path);
              }}
              disabled={disabled || !hasChildren}
              aria-label={i18nMessage("i18n:govoplan-files.value_value.dca59cc0", { value0: expanded ? "i18n:govoplan-files.collapse.9cf188d3" : "i18n:govoplan-files.expand.9869e506", value1: node.name })}>
              
              {expanded ? <FolderOpen size={15} aria-hidden="true" /> : <Folder size={15} aria-hidden="true" />}
            </button>
            <button
              type="button"
              className="file-tree-node"
              onClick={() => onOpen(spaceId, node.path)}
              disabled={disabled}>
              
              <span>{node.name}</span>
            </button>
          </div>);

      })}
      {virtualRows.bottomSpacerHeight > 0 &&
      <div className="managed-file-virtual-spacer" style={{ height: virtualRows.bottomSpacerHeight }} aria-hidden="true" />
      }
    </div>);

}

const ChooserFileBrowser = memo(function ChooserFileBrowser({
  mode,
  loading,
  currentFolder,
  effectiveRoot,
  sortedEntries,
  sortColumn,
  sortDirection,
  linkTarget,
  selectedExactPattern,
  onOpenFolder,
  onToggleSort,
  onRequestExactFile













}: {mode: ManagedFileChooserMode;loading: boolean;currentFolder: string;effectiveRoot: string;sortedEntries: ChooserExplorerEntry[];sortColumn: SortColumn;sortDirection: SortDirection;linkTarget?: FilesManagedFileLinkTarget;selectedExactPattern: string;onOpenFolder: (path: string) => void;onToggleSort: (column: SortColumn) => void;onRequestExactFile: (file: ManagedFile) => void;}) {
  const resetKey = `${mode}:${currentFolder}:${sortColumn}:${sortDirection}:${sortedEntries.length}`;
  const virtualRows = useFixedVirtualRows<HTMLButtonElement>({
    itemCount: sortedEntries.length,
    resetKey,
    rowGap: CHOOSER_FILE_ROW_GAP,
    leadingRows: 1,
    defaultRowHeight: DEFAULT_CHOOSER_FILE_ROW_HEIGHT
  });

  function renderEntry(entry: ChooserExplorerEntry, position: number) {
    if (entry.kind === "folder") {
      return (
        <button
          type="button"
          key={entry.id}
          className="managed-file-entry is-folder"
          onClick={() => onOpenFolder(entry.path)}
          aria-posinset={position}
          aria-setsize={sortedEntries.length + 1}>
          
          <Folder size={18} aria-hidden="true" />
          <span className="managed-file-entry-name"><strong>{entry.name}</strong><small>{entry.fileCount} i18n:govoplan-files.file_s.ca61e97f {entry.folderCount} i18n:govoplan-files.folder_s.10e54730</small></span>
          <span className="managed-file-entry-size">{formatBytes(entry.totalSize)}</span>
          <span className="managed-file-entry-modified">{formatDate(entry.updatedAt)}</span>
        </button>);

    }
    const exactPattern = relativePath(entry.file.display_path, effectiveRoot);
    const selected = mode === "attachment" && selectedExactPattern === exactPattern;
    const linked = isLinkedToTarget(entry.file, linkTarget);
    return (
      <button
        type="button"
        key={entry.file.id}
        className={`managed-file-entry is-file ${selected ? "is-selected" : ""} ${mode === "folder" ? "is-context-only" : ""}`}
        onClick={() => mode === "attachment" ? onRequestExactFile(entry.file) : undefined}
        disabled={mode === "folder"}
        aria-label={mode === "folder" ? i18nMessage("i18n:govoplan-files.value_file_shown_for_context.3d6e0acb", { value0: entry.file.filename }) : entry.file.filename}
        aria-posinset={position}
        aria-setsize={sortedEntries.length + 1}>
        
        <File size={18} aria-hidden="true" />
        <span className="managed-file-entry-name">
          <strong>{entry.file.filename}</strong>
          <small>{linked ? <><Link2 size={12} aria-hidden="true" /> i18n:govoplan-files.linked_to.ca188768 {linkTarget?.label || "target"}</> : "i18n:govoplan-files.file.2c3cafa4"}</small>
        </span>
        <span className="managed-file-entry-size">{formatBytes(entry.file.size_bytes)}</span>
        <span className="managed-file-entry-modified">{formatDate(entry.file.updated_at)}</span>
      </button>);

  }

  return (
    <div className="managed-file-entry-table">
      <div className="managed-file-entry-head" role="row">
        <span aria-hidden="true" />
        <ChooserSortButton column="name" label="i18n:govoplan-files.name.709a2322" activeColumn={sortColumn} direction={sortDirection} onSort={onToggleSort} />
        <ChooserSortButton column="size" label="i18n:govoplan-files.size.b7152342" activeColumn={sortColumn} direction={sortDirection} onSort={onToggleSort} />
        <ChooserSortButton column="modified" label="i18n:govoplan-files.modified.19a532c8" activeColumn={sortColumn} direction={sortDirection} onSort={onToggleSort} />
      </div>
      <div
        className="managed-file-entry-list"
        ref={virtualRows.viewportRef}
        role="listbox"
        aria-label={mode === "folder" ? "i18n:govoplan-files.folders_and_files.d107ac99" : "i18n:govoplan-files.managed_files.4dd6ad11"}
        onScroll={(event) => virtualRows.setScrollTop(event.currentTarget.scrollTop)}>
        
        <button
          type="button"
          ref={virtualRows.measureRowRef}
          className="managed-file-entry is-folder is-parent"
          onClick={() => onOpenFolder(parentWithinRoot(currentFolder, effectiveRoot))}
          disabled={loading || currentFolder === effectiveRoot}
          aria-posinset={1}
          aria-setsize={sortedEntries.length + 1}>
          
          <FolderOpen size={18} aria-hidden="true" />
          <span className="managed-file-entry-name"><strong>..</strong><small>i18n:govoplan-files.parent_folder.d8ed4c2a</small></span>
          <span className="managed-file-entry-size">-</span>
          <span className="managed-file-entry-modified">-</span>
        </button>
        {virtualRows.topSpacerHeight > 0 &&
        <div className="managed-file-virtual-spacer" style={{ height: virtualRows.topSpacerHeight }} aria-hidden="true" />
        }
        {sortedEntries.slice(virtualRows.startIndex, virtualRows.endIndex).map((entry, index) =>
        renderEntry(entry, virtualRows.startIndex + index + 2)
        )}
        {virtualRows.bottomSpacerHeight > 0 &&
        <div className="managed-file-virtual-spacer" style={{ height: virtualRows.bottomSpacerHeight }} aria-hidden="true" />
        }
        {!loading && sortedEntries.length === 0 &&
        <p className="managed-file-empty muted">i18n:govoplan-files.this_folder_is_empty_upload_files_in_the_top_lev.cb6c3a1a</p>
        }
      </div>
    </div>);

});

function PatternResultList({ files, linkTarget, onChoose }: {files: ManagedFile[];linkTarget?: FilesManagedFileLinkTarget;onChoose: (file: ManagedFile) => void;}) {
  const resetKey = `${files.length}:${files[0]?.id ?? ""}:${files[files.length - 1]?.id ?? ""}`;
  const virtualRows = useFixedVirtualRows<HTMLButtonElement>({
    itemCount: files.length,
    resetKey,
    defaultRowHeight: DEFAULT_PATTERN_RESULT_ROW_HEIGHT
  });

  return (
    <div className="managed-pattern-results" role="table" aria-label="i18n:govoplan-files.pattern_preview_results.56cea56c" aria-rowcount={files.length + 1}>
      <div className="file-list-table-head" role="row">
        <span>i18n:govoplan-files.name.709a2322</span><span>i18n:govoplan-files.size.b7152342</span><span>i18n:govoplan-files.modified.19a532c8</span>
      </div>
      <div
        className="file-list-table"
        ref={virtualRows.viewportRef}
        onScroll={(event) => virtualRows.setScrollTop(event.currentTarget.scrollTop)}>
        
        {files.length === 0 && <div className="file-list-empty">i18n:govoplan-files.no_current_files_match_this_pattern.b84c5836</div>}
        {virtualRows.topSpacerHeight > 0 &&
        <div className="managed-file-virtual-spacer" style={{ height: virtualRows.topSpacerHeight }} aria-hidden="true" />
        }
        {files.slice(virtualRows.startIndex, virtualRows.endIndex).map((file, index) =>
        <button
          type="button"
          className="file-list-row file-row managed-pattern-result-row"
          role="row"
          key={file.id}
          ref={index === 0 ? virtualRows.measureRowRef : undefined}
          onClick={() => onChoose(file)}
          aria-rowindex={virtualRows.startIndex + index + 2}>
          
            <span className="file-list-name-cell">
              <span className="file-list-name">
                <File className="file-row-icon" size={20} aria-hidden="true" />
                <span><strong>{file.display_path}</strong>{isLinkedToTarget(file, linkTarget) && <small>i18n:govoplan-files.linked_to.e7ca9e02 {linkTarget?.label || "target"}</small>}</span>
              </span>
            </span>
            <span>{formatBytes(file.size_bytes)}</span>
            <span className="file-row-tail"><span>{formatDate(file.updated_at)}</span></span>
          </button>
        )}
        {virtualRows.bottomSpacerHeight > 0 &&
        <div className="managed-file-virtual-spacer" style={{ height: virtualRows.bottomSpacerHeight }} aria-hidden="true" />
        }
      </div>
    </div>);

}

function useFixedVirtualRows<T extends HTMLElement>({
  itemCount,
  resetKey,
  defaultRowHeight,
  rowGap = 0,
  leadingRows = 0






}: {itemCount: number;resetKey: string;defaultRowHeight: number;rowGap?: number;leadingRows?: number;}) {
  const viewportRef = useRef<HTMLDivElement | null>(null);
  const measureRowRef = useRef<T | null>(null);
  const [viewportHeight, setViewportHeight] = useState(0);
  const [scrollTop, setScrollTop] = useState(0);
  const [rowHeight, setRowHeight] = useState(defaultRowHeight);

  const virtualRows = useMemo(() => {
    const rowExtent = Math.max(1, rowHeight + rowGap);
    const itemScrollTop = Math.max(0, scrollTop - leadingRows * rowExtent);
    const effectiveViewportHeight = Math.max(viewportHeight, rowExtent * CHOOSER_FILE_OVERSCAN_ROWS);
    const rawStartIndex = Math.max(0, Math.floor(itemScrollTop / rowExtent) - CHOOSER_FILE_OVERSCAN_ROWS);
    const startIndex = Math.min(itemCount, rawStartIndex);
    const endIndex = Math.min(
      itemCount,
      Math.max(startIndex, Math.ceil((itemScrollTop + effectiveViewportHeight) / rowExtent) + CHOOSER_FILE_OVERSCAN_ROWS)
    );

    return {
      startIndex,
      endIndex,
      topSpacerHeight: startIndex * rowExtent,
      bottomSpacerHeight: Math.max(0, (itemCount - endIndex) * rowExtent)
    };
  }, [itemCount, leadingRows, rowGap, rowHeight, scrollTop, viewportHeight]);

  useEffect(() => {
    const viewport = viewportRef.current;
    if (!viewport) return undefined;

    const updateViewportHeight = () => {
      setViewportHeight(viewport.clientHeight);
      setScrollTop(viewport.scrollTop);
    };

    updateViewportHeight();
    if (typeof ResizeObserver === "undefined") return undefined;

    const observer = new ResizeObserver(updateViewportHeight);
    observer.observe(viewport);
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    const row = measureRowRef.current;
    if (!row) {
      setRowHeight(defaultRowHeight);
      return undefined;
    }

    const updateRowHeight = () => {
      const measuredHeight = row.getBoundingClientRect().height;
      if (measuredHeight > 0) setRowHeight(Math.round(measuredHeight));
    };

    updateRowHeight();
    if (typeof ResizeObserver === "undefined") return undefined;

    const observer = new ResizeObserver(updateRowHeight);
    observer.observe(row);
    return () => observer.disconnect();
  }, [defaultRowHeight, resetKey]);

  useEffect(() => {
    const viewport = viewportRef.current;
    if (!viewport) return;
    viewport.scrollTop = 0;
    setScrollTop(0);
  }, [resetKey]);

  return {
    ...virtualRows,
    viewportRef,
    measureRowRef,
    setScrollTop
  };
}

function flattenVisibleFolderTree(
nodes: FolderNode[],
spaceId: string,
expandedKeys: Set<string>,
depth = 1,
rows: VirtualFolderTreeRow[] = [])
: VirtualFolderTreeRow[] {
  for (const node of nodes) {
    rows.push({ node, depth });
    if (node.children.length > 0 && expandedKeys.has(treeNodeKey(spaceId, node.path))) {
      flattenVisibleFolderTree(node.children, spaceId, expandedKeys, depth + 1, rows);
    }
  }
  return rows;
}

function sourceMatches(space: FileSpace, source: ManagedFileSource | null): boolean {
  return Boolean(source && space.owner_type === source.ownerType && space.owner_id === source.ownerId);
}

function encodeManagedFileSource(space: Pick<FileSpace, "owner_type" | "owner_id">): string {
  return `${MANAGED_FILE_SOURCE_PREFIX}${space.owner_type}:${space.owner_id}`;
}

function parseManagedFileSource(value: unknown): ManagedFileSource | null {
  if (typeof value !== "string" || !value.startsWith(MANAGED_FILE_SOURCE_PREFIX)) return null;
  const [, ownerType, ...ownerIdParts] = value.split(":");
  const ownerId = ownerIdParts.join(":").trim();
  if (ownerType !== "user" && ownerType !== "group" || !ownerId) return null;
  return { ownerType, ownerId };
}

function normalizeManagedBasePath(value: string): string {
  const normalized = normalizeFolder(value);
  return normalized === "." ? "" : normalized;
}

function relativePath(path: string, root: string): string {
  const normalizedPath = normalizeFilePath(path);
  const normalizedRoot = normalizeFolder(root);
  if (!normalizedRoot) return normalizedPath;
  const prefix = `${normalizedRoot}/`;
  return normalizedPath.startsWith(prefix) ? normalizedPath.slice(prefix.length) : normalizedPath;
}

function chooserBreadcrumbs(folder: string, root: string): {name: string;path: string;}[] {
  const normalizedFolder = normalizeFolder(folder);
  const normalizedRoot = normalizeFolder(root);
  const relative = normalizedRoot && normalizedFolder.startsWith(`${normalizedRoot}/`) ?
  normalizedFolder.slice(normalizedRoot.length + 1) :
  normalizedRoot === normalizedFolder ? "" : normalizedFolder;
  const parts = relative.split("/").filter(Boolean);
  return parts.map((name, index) => ({
    name,
    path: normalizeFolder([normalizedRoot, ...parts.slice(0, index + 1)].filter(Boolean).join("/"))
  }));
}

function parentWithinRoot(folder: string, root: string): string {
  const normalizedFolder = normalizeFolder(folder);
  const normalizedRoot = normalizeFolder(root);
  if (!normalizedFolder || normalizedFolder === normalizedRoot) return normalizedRoot;
  const parent = parentFolderPath(normalizedFolder);
  return isWithinRoot(parent, normalizedRoot) ? parent : normalizedRoot;
}

function isWithinRoot(path: string, root: string): boolean {
  const normalizedPath = normalizeFolder(path);
  const normalizedRoot = normalizeFolder(root);
  return !normalizedRoot || normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}/`);
}

function nodesWithinRoot(nodes: FolderNode[], root: string): FolderNode[] {
  const normalizedRoot = normalizeFolder(root);
  if (!normalizedRoot) return nodes;
  const node = findNode(nodes, normalizedRoot);
  return node?.children ?? [];
}

function findNode(nodes: FolderNode[], path: string): FolderNode | null {
  for (const node of nodes) {
    if (node.path === path) return node;
    const child = findNode(node.children, path);
    if (child) return child;
  }
  return null;
}

function isExactPattern(value: string): boolean {
  return Boolean(value) && !/[?*\[\]{}]/.test(value);
}

function renderChooserPattern(
pattern: string,
previewContext?: Record<string, string>,
renderPatternPreview?: FilesManagedFileChooserProps["renderPatternPreview"])
: string {
  const trimmed = pattern.trim();
  if (!trimmed) return trimmed;
  return (renderPatternPreview?.(trimmed, previewContext) ?? trimmed).trim();
}

function isLinkedToTarget(file: ManagedFile, target?: FilesManagedFileLinkTarget): boolean {
  if (!target) return false;
  return Boolean(file.shares?.some((share) =>
    share.target_type === target.type
    && share.target_id === target.id
    && (typeof share.active === "boolean"
      ? share.active
      : !share.revoked_at
        && (!share.expires_at || new Date(share.expires_at).getTime() > Date.now()))
  ));
}

function readRememberedState(key: string): RememberedChooserState {
  if (typeof window === "undefined") return {};
  try {
    const parsed = JSON.parse(window.localStorage.getItem(key) || "{}") as RememberedChooserState;
    return parsed && typeof parsed === "object" ? parsed : {};
  } catch {
    return {};
  }
}

function writeRememberedState(key: string, state: RememberedChooserState) {
  if (typeof window === "undefined") return;
  try {
    window.localStorage.setItem(key, JSON.stringify(state));
  } catch {


    // Storage is optional; chooser behavior remains functional without it.
  }}
function errorMessage(reason: unknown): string {
  return reason instanceof Error ? reason.message : String(reason);
}
