import { useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { ArrowDown, ArrowUp, ArrowUpDown, File, Folder, FolderOpen, Home, Link2, Search } from "lucide-react";
import { usePlatformUiCapability, type FilesFileExplorerUiCapability } from "@govoplan/core-webui";
import type { ApiSettings } from "../../../types";
import {
  listFileSpaces,
  listFiles,
  listFolders,
  resolveFilePatterns,
  shareFileWithCampaign,
  type FileFolder,
  type FileSpace,
  type ManagedFile
} from "../../../api/files";
import { Button } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui";
import { FolderTree } from "../../files/components/FileManagerComponents";
import { useFileTreeState } from "../../files/hooks/useFileTreeState";
import type { FolderNode, SortColumn, SortDirection } from "../../files/types";
import {
  buildExplorerEntries,
  buildFolderTree,
  formatBytes,
  formatDate,
  normalizeFilePath,
  normalizeFolder,
  parentFolderPath,
  sortExplorerEntries
} from "../../files/utils/fileManagerUtils";
import {
  encodeManagedAttachmentSource,
  parseManagedAttachmentSource,
  type ManagedAttachmentSource
} from "../utils/attachments";

type ManagedFileChooserMode = "folder" | "attachment";
type AttachmentChoiceMode = "file" | "pattern";

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

export type ManagedFolderSelection = {
  space: FileSpace;
  folderPath: string;
  source: string;
};

export type ManagedAttachmentSelection = ManagedFolderSelection & {
  fileFilter: string;
  matchCount: number;
  fileIds: string[];
  selectionType: AttachmentChoiceMode;
};

type ManagedFileChooserProps = {
  open: boolean;
  settings: ApiSettings;
  campaignId: string;
  mode: ManagedFileChooserMode;
  source?: string;
  basePath?: string;
  initialPattern?: string;
  rememberKey?: string;
  onClose: () => void;
  onSelectFolder?: (selection: ManagedFolderSelection) => void;
  onSelectAttachment?: (selection: ManagedAttachmentSelection) => void;
};

export default function ManagedFileChooser({
  open,
  settings,
  campaignId,
  mode,
  source,
  basePath = ".",
  initialPattern = "",
  rememberKey = mode,
  onClose,
  onSelectFolder,
  onSelectAttachment
}: ManagedFileChooserProps) {
  const filesExplorerUi = usePlatformUiCapability<FilesFileExplorerUiCapability>("files.fileExplorer");
  const FolderTreeComponent = filesExplorerUi?.FolderTree ?? FolderTree;
  const parsedSource = useMemo(() => parseManagedAttachmentSource(source), [source]);
  const normalizedBasePath = normalizeManagedBasePath(basePath);
  const storageKey = useMemo(
    () => `multimailer.managedFileChooser.${campaignId}.${rememberKey}`,
    [campaignId, rememberKey]
  );
  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 [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
    ? !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 { expandedTreeNodes, toggleTreeFolder } = useFileTreeState({
    activeSpaceId: selectedSpaceId,
    currentFolder,
    onOpenFolder: (_spaceId, path) => openFolder(path)
  });

  useEffect(() => {
    if (!open) return;
    let cancelled = false;
    setLoading(true);
    setError("");
    setPattern(remembered.pattern ?? initialPattern.trim());
    setPatternMatches(null);
    setPendingExactFile(null);
    void listFileSpaces(settings)
      .then((response) => {
        if (cancelled) return;
        setSpaces(response.spaces);
        const sourceSpace = response.spaces.find((item) => sourceMatches(item, parsedSource));
        const rememberedSpace = response.spaces.find((item) => item.id === remembered.spaceId);
        const firstSpace = sourceSpace ?? rememberedSpace ?? response.spaces[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 Promise.all([
      listFiles(settings, { owner_type: selectedSpace.owner_type, owner_id: selectedSpace.owner_id }),
      listFolders(settings, { owner_type: selectedSpace.owner_type, owner_id: selectedSpace.owner_id })
    ])
      .then(([fileResponse, folderResponse]) => {
        if (cancelled) return;
        setFiles(fileResponse.files);
        setFolders(folderResponse.folders);
      })
      .catch((reason: unknown) => {
        if (!cancelled) setError(errorMessage(reason));
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => { cancelled = true; };
  }, [effectiveRoot, open, selectedSpace?.id, settings.apiBaseUrl, settings.apiKey, settings.accessToken, storageKey]);

  useEffect(() => {
    if (!open || !selectedSpaceId) return;
    writeRememberedState(storageKey, { spaceId: selectedSpaceId, folder: currentFolder, pattern });
  }, [currentFolder, open, pattern, selectedSpaceId, storageKey]);

  function toggleSort(column: SortColumn) {
    if (sortColumn === column) {
      setSortDirection((current) => current === "asc" ? "desc" : "asc");
      return;
    }
    setSortColumn(column);
    setSortDirection(column === "name" ? "asc" : "desc");
  }

  function openFolder(path: string) {
    const normalized = normalizeFolder(path);
    if (mode === "attachment" && effectiveRoot && !isWithinRoot(normalized, effectiveRoot)) return;
    setCurrentFolder(normalized);
    setPatternMatches(null);
  }

  async function previewPattern(): Promise<ManagedFile[]> {
    if (!selectedSpace) return [];
    const trimmed = pattern.trim();
    if (!trimmed) {
      setPatternMatches([]);
      return [];
    }
    setResolving(true);
    setError("");
    try {
      const response = await resolveFilePatterns(settings, {
        patterns: [trimmed],
        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);
    }
  }

  function requestExactFile(file: ManagedFile) {
    const exactPattern = relativePath(file.display_path, effectiveRoot);
    const currentPattern = pattern.trim();
    if (currentPattern && currentPattern !== exactPattern) {
      setPendingExactFile(file);
      return;
    }
    applyExactFile(file);
  }

  function applyExactFile(file: ManagedFile) {
    setPattern(relativePath(file.display_path, effectiveRoot));
    setPatternMatches([file]);
    setPendingExactFile(null);
  }

  async function shareMatches(matches: ManagedFile[]) {
    const toShare = matches.filter((file) => !file.shares?.some((share) => (
      share.target_type === "campaign" && share.target_id === campaignId && !share.revoked_at
    )));
    await Promise.all(toShare.map((file) => shareFileWithCampaign(settings, file.id, campaignId)));
  }

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

      const matches = patternMatches ?? await previewPattern();
      await shareMatches(matches);
      const trimmedPattern = pattern.trim();
      onSelectAttachment?.({
        space: selectedSpace,
        folderPath: effectiveRoot,
        source: encodeManagedAttachmentSource(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" ? "Choose managed attachment source" : "Choose managed file or pattern"}
        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}>Cancel</Button>
            <Button
              variant="primary"
              onClick={() => void confirmSelection()}
              disabled={loading || submitting || !selectedSpace || !sourceMatchesSpace || (mode === "attachment" && (!parsedSource || !pattern.trim()))}
            >
              {submitting ? "Linking…" : mode === "folder" ? "Use this folder" : "Use pattern"}
            </Button>
          </>
        )}
      >
        {error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
        {mode === "attachment" && !parsedSource && (
          <DismissibleAlert tone="warning" dismissible={false}>
            This attachment base path is not connected to a managed file space. Choose its folder under <strong>Attachments → Attachment sources</strong> first.
          </DismissibleAlert>
        )}
        {mode === "attachment" && parsedSource && !sourceMatchesSpace && !loading && (
          <DismissibleAlert tone="danger" dismissible={false}>The configured managed file space is no longer available to this user.</DismissibleAlert>
        )}

        <div className="managed-file-chooser-layout" aria-busy={loading}>
          <aside className="managed-file-chooser-spaces file-tree-panel" aria-label="File spaces and folders">
            <div className="file-tree-heading">Spaces</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" 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 && (
                      <FolderTreeComponent
                        nodes={treeNodes}
                        activeSpaceId={selectedSpaceId}
                        spaceId={space.id}
                        currentFolder={currentFolder}
                        expandedKeys={expandedTreeNodes}
                        onOpen={(_spaceId, path) => openFolder(path)}
                        onToggle={toggleTreeFolder}
                        dragDropEnabled={false}
                        contextMenuEnabled={false}
                        disabled={loading}
                      />
                    )}
                  </div>
                );
              })}
              {!loading && spaces.length === 0 && <p className="muted small-note">No accessible file spaces were found.</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="Current folder">
                <button type="button" onClick={() => openFolder(effectiveRoot)} disabled={loading}>
                  <Home size={14} aria-hidden="true" /> {selectedSpace?.label || "Files"}
                </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" && (
              <div className="managed-pattern-editor">
                <label>
                  <span>File or pattern relative to <code>{effectiveRoot || "."}</code></span>
                  <div className="field-with-action split-field-action">
                    <input
                      value={pattern}
                      onChange={(event) => { setPattern(event.target.value); setPatternMatches(null); }}
                      onKeyDown={(event) => { if (event.key === "Enter") void previewPattern(); }}
                      placeholder="folder/file.pdf or **/*.pdf"
                    />
                    <Button onClick={() => void previewPattern()} disabled={resolving || !pattern.trim()}>
                      <Search size={15} aria-hidden="true" /> {resolving ? "Checking…" : "Preview"}
                    </Button>
                  </div>
                </label>
                {patternMatches !== null && (
                  <div className="managed-pattern-summary">
                    <span>{patternMatches.length} current file{patternMatches.length === 1 ? "" : "s"} match.</span>
                    <Button variant="ghost" onClick={() => setPatternMatches(null)}>Back to folder</Button>
                  </div>
                )}
              </div>
            )}

            {patternMatches !== null && mode === "attachment" ? (
              <PatternResultList files={patternMatches} campaignId={campaignId} onChoose={requestExactFile} />
            ) : (
              <div className="managed-file-entry-table">
                <div className="managed-file-entry-head" role="row">
                  <span aria-hidden="true" />
                  <ChooserSortButton column="name" label="Name" activeColumn={sortColumn} direction={sortDirection} onSort={toggleSort} />
                  <ChooserSortButton column="size" label="Size" activeColumn={sortColumn} direction={sortDirection} onSort={toggleSort} />
                  <ChooserSortButton column="modified" label="Modified" activeColumn={sortColumn} direction={sortDirection} onSort={toggleSort} />
                </div>
                <div className="managed-file-entry-list" role="listbox" aria-label={mode === "folder" ? "Folders and files" : "Managed files"}>
                  <button
                    type="button"
                    className="managed-file-entry is-folder is-parent"
                    onClick={() => openFolder(parentWithinRoot(currentFolder, effectiveRoot))}
                    disabled={loading || currentFolder === effectiveRoot}
                  >
                    <FolderOpen size={18} aria-hidden="true" />
                    <span className="managed-file-entry-name"><strong>..</strong><small>Parent folder</small></span>
                    <span className="managed-file-entry-size">—</span>
                    <span className="managed-file-entry-modified">—</span>
                  </button>
                  {sortedEntries.map((entry) => {
                    if (entry.kind === "folder") {
                      return (
                        <button type="button" key={entry.id} className="managed-file-entry is-folder" onClick={() => openFolder(entry.path)}>
                          <Folder size={18} aria-hidden="true" />
                          <span className="managed-file-entry-name"><strong>{entry.name}</strong><small>{entry.fileCount} file(s), {entry.folderCount} folder(s)</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" && pattern.trim() === exactPattern;
                    const campaignShared = isSharedWithCampaign(entry.file, campaignId);
                    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" ? requestExactFile(entry.file) : undefined}
                        disabled={mode === "folder"}
                        aria-label={mode === "folder" ? `${entry.file.filename}, file shown for context` : entry.file.filename}
                      >
                        <File size={18} aria-hidden="true" />
                        <span className="managed-file-entry-name">
                          <strong>{entry.file.filename}</strong>
                          <small>{campaignShared ? <><Link2 size={12} aria-hidden="true" /> linked to campaign</> : "File"}</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>
                    );
                  })}
                  {!loading && sortedEntries.length === 0 && (
                    <p className="managed-file-empty muted">This folder is empty. Upload files in the top-level Files module.</p>
                  )}
                </div>
              </div>
            )}

            {mode === "folder" && (
              <p className="muted small-note managed-file-chooser-note">Choose the folder that should act as this campaign attachment source.</p>
            )}
            {mode === "attachment" && (
              <p className="muted small-note managed-file-chooser-note">Clicking a file sets its exact relative path as the pattern. Preview resolves the pattern against the current managed file space.</p>
            )}
          </section>
        </div>
      </Dialog>

      <ConfirmDialog
        open={Boolean(pendingExactFile)}
        title="Replace the current pattern?"
        message={pendingExactFile ? `Replace “${pattern.trim()}” with the exact file “${relativePath(pendingExactFile.display_path, effectiveRoot)}”?` : "Replace the current pattern?"}
        confirmLabel="Replace pattern"
        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={`${label}: ${active ? `sorted ${direction === "asc" ? "ascending" : "descending"}` : "not sorted"}. Activate to sort.`}
      onClick={() => onSort(column)}
    >
      <span>{label}</span><Icon size={14} aria-hidden="true" />
    </button>
  );
}

function PatternResultList({ files, campaignId, onChoose }: { files: ManagedFile[]; campaignId: string; onChoose: (file: ManagedFile) => void }) {
  return (
    <div className="managed-pattern-results" role="table" aria-label="Pattern preview results">
      <div className="file-list-table-head" role="row">
        <span>Name</span><span>Size</span><span>Modified</span>
      </div>
      <div className="file-list-table">
        {files.length === 0 && <div className="file-list-empty">No current files match this pattern.</div>}
        {files.map((file) => (
          <button type="button" className="file-list-row file-row managed-pattern-result-row" role="row" key={file.id} onClick={() => onChoose(file)}>
            <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>{isSharedWithCampaign(file, campaignId) && <small>Linked to campaign</small>}</span>
              </span>
            </span>
            <span>{formatBytes(file.size_bytes)}</span>
            <span className="file-row-tail"><span>{formatDate(file.updated_at)}</span></span>
          </button>
        ))}
      </div>
    </div>
  );
}

function sourceMatches(space: FileSpace, source: ManagedAttachmentSource | null): boolean {
  return Boolean(source && space.owner_type === source.ownerType && space.owner_id === source.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 isSharedWithCampaign(file: ManagedFile, campaignId: string): boolean {
  return Boolean(file.shares?.some((share) => share.target_type === "campaign" && share.target_id === campaignId && !share.revoked_at));
}

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);
}
