import * as Select from "@radix-ui/react-select";
import {
  IconLock,
  IconWorld,
  IconTrash,
  IconCheck,
  IconChevronDown,
  IconCopy,
  IconLoader2,
  IconSearch,
  IconSearchOff,
  IconShare3,
  IconUsersGroup,
} from "@tabler/icons-react";
import {
  type ComponentPropsWithoutRef,
  useEffect,
  useId,
  useRef,
  useState,
  type ReactNode,
} from "react";
import type {
  KeyboardEvent as ReactKeyboardEvent,
  UIEvent as ReactUIEvent,
} from "react";

import { writeClipboardText } from "../clipboard.js";
import {
  Popover,
  PopoverAnchor,
  PopoverContent,
  PopoverTrigger,
} from "../components/ui/popover.js";
import { cn } from "../utils.js";
import {
  useShareButtonController,
  type ShareButtonController,
  type ShareButtonOrgMember,
  type ShareButtonOrgMemberSearch,
  type ShareButtonShare,
  type ShareButtonRole,
  type ShareButtonVisibility,
} from "./useShareButtonController.js";

export interface ShareButtonProps {
  resourceType: string;
  resourceId: string;
  resourceTitle?: string;
  /** @deprecated No longer affects rendering — trigger always says
   *  "Share". Kept for callsite compatibility. */
  variant?: "compact" | "label";
  /** Optional trigger style. Defaults to a text-only "Share" label.
   *  "label-icon" opts into an icon plus label; "icon" is icon-only. */
  trigger?: "label" | "icon" | "label-icon";
  /** @deprecated No longer affects rendering — kept for callsite compatibility. */
  hideTriggerIcon?: boolean;
  /** Optional className applied to the trigger button. */
  triggerClassName?: string;
  /** Notified when the share popover opens or closes. Hosts that render the
   *  button next to an iframe use this to disable the iframe's pointer events
   *  while the popover is open, so popover hover/clicks aren't swallowed. */
  onOpenChange?: (open: boolean) => void;
  /** Open the popover on first render. Useful after an upgrade/create flow that
   *  lands the user directly in the shareable resource. */
  defaultOpen?: boolean;
  /** Optional public/share URL shown as a copyable link in the popover.
   *  This is treated as the primary "Copy link" target — same convention
   *  as Google Docs' Share dialog, which copies the editor URL. */
  shareUrl?: string;
  /** Optional label for the primary copyable link section. */
  shareUrlLabel?: string;
  /** Optional helper text for the primary copyable link section. */
  shareUrlDescription?: ReactNode;
  /** Where to render share links in the popover. Defaults to the bottom,
   *  matching the historical Google-Docs-style share dialog. */
  shareUrlPlacement?: "top" | "bottom";
  /** Whether to render copyable share URL fields. Defaults to true. */
  showShareLinks?: boolean;
  /** @deprecated The Done action was removed; share popovers dismiss directly. */
  showDoneButton?: boolean;
  /** Optional placeholder shown in the share-URL slot when `shareUrl` is
   *  undefined. Use this to explain *why* there's no link yet (e.g. "Publish
   *  this form to get a public response link") instead of leaving the slot
   *  empty. */
  shareUrlPlaceholder?: ReactNode;
  /** Optional secondary copyable link (e.g. a presentation / read-only
   *  surface for the same resource). Anyone with at least viewer access
   *  can open it — access is enforced on the resource itself, not the
   *  URL shape, so we never gate this behind visibility. */
  secondaryShareUrl?: string;
  /** Optional label for the secondary copyable link. */
  secondaryShareUrlLabel?: string;
  /** Optional helper text for the secondary copyable link. */
  secondaryShareUrlDescription?: ReactNode;
  /** @deprecated No longer enforced — access is checked on the resource,
   *  not the URL shape, mirroring Google Slides. Kept for callsite
   *  compatibility; the prop is now a no-op. */
  shareUrlRequiresPublic?: boolean;
  /** @deprecated See `shareUrlRequiresPublic`. No longer rendered. */
  shareUrlUnavailableDescription?: ReactNode;
  /** Optional template-specific copy for the visibility picker. */
  visibilityCopy?: Partial<
    Record<Visibility, { label?: string; description?: string }>
  >;
  /** Optional label for the explicit per-person access list. */
  peopleAccessLabel?: ReactNode;
  /** Optional label for the coarse visibility control. */
  generalAccessLabel?: ReactNode;
  /** Optional note rendered between general access and the copyable link. */
  accessNote?: ReactNode;
  /** Optional host-rendered footer for compact app-specific share actions. */
  shareFooterContent?: ReactNode;
  /** Optional Notion-style organization access control. When present, the
   *  share panel exposes a "Hide in search" switch under Advanced for org
   *  visibility. */
  hideInSearchControl?: {
    checked: boolean;
    pending?: boolean;
    label?: string;
    description?: ReactNode;
    onCheckedChange: (checked: boolean) => void | Promise<void>;
  };
  /** Optional extra tabs rendered beside the default sharing/access panel. */
  shareTabs?: {
    shareLabel?: ReactNode;
    defaultValue?: string;
    tabs: Array<{
      value: string;
      label: ReactNode;
      content: ReactNode;
      disabled?: boolean;
    }>;
    onValueChange?: (value: string) => void;
  };
  /** Optional className for the popover content, useful for wider custom tabs. */
  popoverClassName?: string;
}

type Visibility = ShareButtonVisibility;
type Role = ShareButtonRole;
type HideInSearchControl = NonNullable<ShareButtonProps["hideInSearchControl"]>;
type OrgMember = ShareButtonOrgMember;
type OrgMemberSearch = ShareButtonOrgMemberSearch;

type Share = ShareButtonShare;

// Match shadcn's <Button size="sm" variant="outline"> sizing so the trigger
// sits flush next to other controls while staying transparent at rest.
const BUTTON_BASE =
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0";
const BUTTON_OUTLINE_SM = cn(
  BUTTON_BASE,
  "h-9 px-3 border border-[hsl(var(--sidebar-border,var(--input)))] bg-transparent text-foreground hover:bg-[hsl(var(--sidebar-accent,var(--accent)))] hover:text-[hsl(var(--sidebar-accent-foreground,var(--accent-foreground)))]",
);
const BUTTON_PRIMARY_SM = cn(
  BUTTON_BASE,
  "h-9 px-4 bg-primary text-primary-foreground hover:bg-primary/90",
);
const BUTTON_GHOST_ICON = cn(
  BUTTON_BASE,
  "h-7 w-7 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
);
const SHARE_POPOVER_SURFACE =
  "border border-border bg-popover text-popover-foreground";
const SHARE_NESTED_OVERLAY_ATTR = "data-agent-native-share-overlay";
const SHARE_NESTED_OVERLAY_Z = "z-[100020]";
const VIS_META: Record<
  Visibility,
  { label: string; description: string; Icon: typeof IconLock }
> = {
  private: {
    label: "Private",
    description: "Only people with access can view",
    Icon: IconLock,
  },
  org: {
    label: "Organization",
    description: "Anyone in your organization can view",
    Icon: IconUsersGroup,
  },
  public: {
    label: "Public",
    description: "Anyone with the link can view",
    Icon: IconWorld,
  },
};

function visibilityMeta(
  visibility: Visibility,
  copy?: ShareButtonProps["visibilityCopy"],
): (typeof VIS_META)[Visibility] {
  const base = VIS_META[visibility];
  const override = copy?.[visibility];
  return {
    ...base,
    label: override?.label ?? base.label,
    description: override?.description ?? base.description,
  };
}

const ROLE_OPTIONS: Array<{ value: Role; label: string; description: string }> =
  [
    { value: "viewer", label: "Viewer", description: "Can view" },
    { value: "editor", label: "Editor", description: "Can edit" },
    {
      value: "admin",
      label: "Admin",
      description: "Can edit and manage access",
    },
  ];

type SharePopoverInteractOutsideEvent = Parameters<
  NonNullable<
    ComponentPropsWithoutRef<typeof PopoverContent>["onInteractOutside"]
  >
>[0];

function isShareNestedOverlayTarget(target: EventTarget | null): boolean {
  return (
    target instanceof Element &&
    target.closest(`[${SHARE_NESTED_OVERLAY_ATTR}]`) !== null
  );
}

function handleSharePopoverInteractOutside(
  event: SharePopoverInteractOutsideEvent,
) {
  const originalTarget = event.detail.originalEvent.target;
  if (isShareNestedOverlayTarget(originalTarget)) {
    event.preventDefault();
  }
}

/**
 * Framework share control. Renders a shadcn-outline-styled trigger that
 * opens a Google-Docs-style popover anchored beneath it. Uses Tailwind
 * + CSS variables so the same component renders natively in light and
 * dark mode in any shadcn template.
 */
export function ShareButton(props: ShareButtonProps) {
  const controller = useShareButtonController({
    resourceType: props.resourceType,
    resourceId: props.resourceId,
    defaultOpen: props.defaultOpen,
    onOpenChange: props.onOpenChange,
    shareTabs: props.shareTabs,
    shareUrl: props.shareUrl,
    hideInSearchControl: props.hideInSearchControl,
  });
  const triggerVisibility = controller.triggerVisibility;
  const triggerMeta = triggerVisibility
    ? visibilityMeta(triggerVisibility, props.visibilityCopy)
    : null;
  const TriggerIcon = triggerMeta?.Icon ?? IconShare3;
  const iconOnly = props.trigger === "icon";
  const showTriggerIcon = iconOnly || props.trigger === "label-icon";
  const triggerLabel =
    iconOnly && triggerMeta ? `Share (${triggerMeta.label})` : "Share";

  return (
    <Popover open={controller.open} onOpenChange={controller.handleOpenChange}>
      <PopoverTrigger asChild>
        <button
          type="button"
          className={cn(
            iconOnly ? BUTTON_GHOST_ICON : BUTTON_OUTLINE_SM,
            props.triggerClassName,
          )}
          aria-label={triggerLabel}
          title={triggerLabel}
        >
          {showTriggerIcon && <TriggerIcon size={16} strokeWidth={1.75} />}
          {!iconOnly && <span>Share</span>}
        </button>
      </PopoverTrigger>
      <PopoverContent
        align="end"
        sideOffset={6}
        data-agent-native-share-overlay=""
        className={cn(
          "z-[2000] w-[min(460px,92vw)] rounded-lg p-4 shadow-lg",
          SHARE_POPOVER_SURFACE,
          props.popoverClassName,
        )}
        onOpenAutoFocus={(e) => e.preventDefault()}
        onInteractOutside={handleSharePopoverInteractOutside}
      >
        <SharePanel {...props} controller={controller} />
      </PopoverContent>
    </Popover>
  );
}

function SharePanel(
  props: ShareButtonProps & {
    controller: ShareButtonController;
  },
) {
  const { resourceTitle, controller } = props;
  const {
    inviteEmail,
    setInviteEmail: onInviteEmailChange,
    activeShareTab,
    handleShareTabChange,
    data,
    policy,
    visibility,
    canManage,
    role,
    setRole,
    notifyPeople,
    setNotifyPeople,
    shareError,
    setShareError,
    suggestionsOpen,
    setSuggestionsOpen,
    inFlight,
    memberSearch,
    memberSuggestions,
    knownMembers,
    shares,
    handleVisibility,
    handleHideInSearch,
    handleAdd,
    handleChangeRole,
    handleRemove,
  } = controller;
  const hasInviteEmail = inviteEmail.trim().length > 0;

  const isLoading = data === undefined;
  const meta = visibilityMeta(visibility, props.visibilityCopy);
  const peopleAccessLabel = props.peopleAccessLabel ?? "People with access";
  const generalAccessLabel = props.generalAccessLabel ?? "General access";
  const shareLinks = (
    <>
      {props.shareUrl ? (
        <CopyLinkField
          value={props.shareUrl}
          label={props.shareUrlLabel}
          description={props.shareUrlDescription}
        />
      ) : props.shareUrlPlaceholder ? (
        <div className="mb-4 rounded-md border border-dashed border-border bg-muted/20 px-3 py-2.5 text-xs text-muted-foreground">
          {props.shareUrlLabel ? (
            <div className="mb-0.5 font-medium text-foreground">
              {props.shareUrlLabel}
            </div>
          ) : null}
          {props.shareUrlPlaceholder}
        </div>
      ) : null}

      {props.secondaryShareUrl ? (
        <CopyLinkField
          value={props.secondaryShareUrl}
          label={props.secondaryShareUrlLabel}
          description={props.secondaryShareUrlDescription}
        />
      ) : null}
    </>
  );
  const showShareLinks =
    (props.showShareLinks ?? true) &&
    (Boolean(props.shareUrl) ||
      Boolean(props.shareUrlPlaceholder) ||
      Boolean(props.secondaryShareUrl));
  const shareUrlPlacement = props.shareUrlPlacement ?? "bottom";
  const extraTabs = props.shareTabs?.tabs ?? [];
  const hasTabs = extraTabs.length > 0;
  const shareTabLabel = props.shareTabs?.shareLabel ?? "Share link";

  const titleText = resourceTitle
    ? `Share "${resourceTitle}"`
    : `Share ${props.resourceType}`;

  const sharePanel = isLoading ? (
    <div>
      {!hasTabs ? (
        <div
          className="mb-3 truncate text-base font-semibold"
          title={titleText}
        >
          {titleText}
        </div>
      ) : null}
      <div className="mb-4 h-9 rounded-md bg-muted animate-pulse" />
      <div className="mb-2 text-sm font-semibold">{peopleAccessLabel}</div>
      <div className="mb-4 h-7 rounded-md bg-muted animate-pulse" />
      <div className="mb-2 text-sm font-semibold">{generalAccessLabel}</div>
      <div className="mb-4 h-9 rounded-md bg-muted animate-pulse" />
    </div>
  ) : (
    <div>
      {!hasTabs ? (
        <div
          className="mb-3 truncate text-base font-semibold"
          title={titleText}
        >
          {titleText}
        </div>
      ) : null}

      {showShareLinks && shareUrlPlacement === "top" ? shareLinks : null}

      {canManage ? (
        <div className="mb-4 space-y-2">
          <div className="flex items-stretch gap-2">
            <MemberAutocomplete
              value={inviteEmail}
              open={suggestionsOpen}
              onOpenChange={setSuggestionsOpen}
              onValueChange={(next) => {
                onInviteEmailChange(next);
                if (shareError) setShareError(null);
              }}
              onSelectMember={(member) => {
                onInviteEmailChange(member.email);
                setSuggestionsOpen(false);
                if (shareError) setShareError(null);
              }}
              onSubmit={handleAdd}
              placeholder={
                policy.requireOrgMemberForUserShares
                  ? "Add people from your organization"
                  : "Add people by email"
              }
              suggestions={memberSuggestions}
              search={memberSearch}
            />
            <RoleSelect value={role} onChange={setRole} />
            <button
              type="button"
              onClick={handleAdd}
              disabled={!hasInviteEmail}
              className={BUTTON_PRIMARY_SM}
            >
              Add
            </button>
          </div>
          {shareError ? (
            <div
              role="alert"
              className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive"
            >
              {shareError}
            </div>
          ) : null}
          {hasInviteEmail ? (
            <label className="inline-flex items-center gap-2 text-xs text-muted-foreground">
              <input
                type="checkbox"
                checked={notifyPeople}
                onChange={(e) => setNotifyPeople(e.target.checked)}
                className="h-4 w-4 rounded border-input accent-primary"
              />
              Notify people
            </label>
          ) : null}
        </div>
      ) : null}

      <div className="mb-2 text-sm font-semibold">{peopleAccessLabel}</div>
      <ul className="mb-4 flex flex-col gap-1 list-none p-0 m-0">
        {data?.ownerEmail ? (
          <li className="flex items-center gap-3 px-1 py-1.5 text-sm">
            <Avatar label={displayName(data.ownerEmail, knownMembers)} />
            <span className="flex-1 min-w-0 truncate">
              {displayName(data.ownerEmail, knownMembers)}
            </span>
            <span className="text-xs text-muted-foreground">Owner</span>
          </li>
        ) : null}
        {shares.map((s) => (
          <li
            key={keyOf(s)}
            className={cn(
              "flex items-center gap-3 px-1 py-1.5 text-sm",
              inFlight.has(keyOf(s)) && "opacity-60",
            )}
          >
            <Avatar
              label={principalLabel(s, knownMembers)}
              org={s.principalType === "org"}
            />
            <span className="flex-1 min-w-0 truncate">
              {principalLabel(s, knownMembers)}
            </span>
            {canManage ? (
              <RoleSelect
                value={s.role}
                onChange={(r) => handleChangeRole(s, r)}
                disabled={inFlight.has(keyOf(s))}
                plain
              />
            ) : (
              <span className="text-xs text-muted-foreground">
                {cap(s.role)}
              </span>
            )}
            {canManage ? (
              <button
                type="button"
                aria-label="Remove"
                onClick={() => handleRemove(s)}
                disabled={inFlight.has(keyOf(s))}
                className={BUTTON_GHOST_ICON}
              >
                <IconTrash size={14} />
              </button>
            ) : null}
          </li>
        ))}
        {!shares.length && !data?.ownerEmail ? (
          <li className="px-1 py-1.5 text-sm text-muted-foreground">
            No one has access yet.
          </li>
        ) : null}
      </ul>

      <div className="mb-2 text-sm font-semibold">{generalAccessLabel}</div>
      <div className="mb-4 flex items-center gap-3">
        <span
          aria-hidden
          className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground"
        >
          <meta.Icon size={16} strokeWidth={1.75} />
        </span>
        <div className="min-w-0 flex-1">
          <VisibilitySelect
            value={visibility}
            onChange={handleVisibility}
            disabled={!canManage}
            visibilityCopy={props.visibilityCopy}
            allowPublic={policy.allowPublic}
          />
          <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
            <span>{meta.description}</span>
            {visibility === "org" && props.hideInSearchControl ? (
              <AdvancedAccessPopover
                control={props.hideInSearchControl}
                canManage={canManage}
                onToggle={handleHideInSearch}
              />
            ) : null}
          </div>
        </div>
      </div>

      {shareError && !canManage ? (
        <div
          role="alert"
          className="mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive"
        >
          {shareError}
        </div>
      ) : null}

      {props.accessNote ? (
        <div className="mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground">
          {props.accessNote}
        </div>
      ) : null}

      {showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null}

      {props.shareFooterContent}
    </div>
  );

  if (!hasTabs) return sharePanel;

  const tabs = [
    {
      value: "share",
      label: shareTabLabel,
      content: sharePanel,
      disabled: false,
    },
    ...extraTabs,
  ];
  const activeTab = tabs.some((tab) => tab.value === activeShareTab)
    ? activeShareTab
    : "share";

  return (
    <div className="flex flex-col gap-4">
      <div
        role="tablist"
        aria-label="Share options"
        className="flex gap-1 rounded-xl bg-muted/70 p-1"
      >
        {tabs.map((tab) => {
          const active = tab.value === activeTab;
          return (
            <button
              key={tab.value}
              type="button"
              role="tab"
              aria-selected={active}
              disabled={tab.disabled}
              onClick={() => handleShareTabChange(tab.value)}
              className={cn(
                "h-11 min-w-0 flex-1 rounded-lg px-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-background/70 hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
                active &&
                  "bg-background text-foreground shadow-sm ring-2 ring-primary",
              )}
            >
              <span className="block truncate">{tab.label}</span>
            </button>
          );
        })}
      </div>
      <div role="tabpanel">
        {tabs.find((tab) => tab.value === activeTab)?.content}
      </div>
    </div>
  );
}

function AdvancedAccessPopover({
  control,
  canManage,
  onToggle,
}: {
  control: HideInSearchControl;
  canManage: boolean;
  onToggle: () => void;
}) {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          type="button"
          disabled={!canManage}
          className="inline-flex items-center gap-1 rounded-sm px-1 py-0.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
        >
          Advanced
          <IconChevronDown size={12} strokeWidth={1.8} />
        </button>
      </PopoverTrigger>
      <PopoverContent
        align="start"
        sideOffset={6}
        data-agent-native-share-overlay=""
        onOpenAutoFocus={(event) => event.preventDefault()}
        className={cn(
          SHARE_NESTED_OVERLAY_Z,
          "w-72 p-3 shadow-lg",
          SHARE_POPOVER_SURFACE,
        )}
      >
        <div className="space-y-3">
          <div>
            <div className="text-sm font-medium text-foreground">
              Advanced access
            </div>
            <div className="mt-1 text-xs leading-5 text-muted-foreground">
              Control how organization access appears in search.
            </div>
          </div>
          <button
            type="button"
            role="switch"
            aria-checked={control.checked}
            disabled={!canManage || control.pending}
            onClick={onToggle}
            className={cn(
              "flex w-full items-start gap-3 rounded-md border border-border/70 bg-card px-3 py-2.5 text-start transition-colors hover:bg-accent/45 disabled:cursor-not-allowed disabled:opacity-60",
              control.checked && "border-border bg-accent/35 text-foreground",
            )}
          >
            <span
              aria-hidden
              className={cn(
                "relative mt-0.5 inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-border bg-muted-foreground/25 transition-colors",
                control.checked && "border-primary/70 bg-primary",
              )}
            >
              <span
                className={cn(
                  "ml-0.5 size-4 rounded-full bg-popover shadow-sm transition-transform",
                  control.checked && "translate-x-4",
                )}
              />
            </span>
            <span className="min-w-0 flex-1">
              <span className="flex items-center gap-1.5 text-sm font-medium text-foreground">
                <IconSearchOff size={14} strokeWidth={1.8} />
                {control.label ?? "Hide in search"}
              </span>
              <span className="mt-0.5 block text-xs leading-5 text-muted-foreground">
                {control.description ??
                  "People with the link can still open this."}
              </span>
            </span>
          </button>
        </div>
      </PopoverContent>
    </Popover>
  );
}

interface MemberAutocompleteProps {
  value: string;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onValueChange: (value: string) => void;
  onSelectMember: (member: OrgMember) => void;
  onSubmit: () => void;
  placeholder: string;
  suggestions: OrgMember[];
  search: OrgMemberSearch;
}

function MemberAutocomplete({
  value,
  open,
  onOpenChange,
  onValueChange,
  onSelectMember,
  onSubmit,
  placeholder,
  suggestions,
  search,
}: MemberAutocompleteProps) {
  const rawListboxId = useId();
  const listboxId = rawListboxId.replace(/:/g, "");
  const inputRef = useRef<HTMLInputElement>(null);
  const [activeIndex, setActiveIndex] = useState(-1);
  const activeMember =
    activeIndex >= 0 && activeIndex < suggestions.length
      ? suggestions[activeIndex]
      : null;

  useEffect(() => {
    setActiveIndex(-1);
  }, [value]);

  useEffect(() => {
    if (activeIndex >= suggestions.length) {
      setActiveIndex(suggestions.length > 0 ? suggestions.length - 1 : -1);
    }
  }, [activeIndex, suggestions.length]);

  useEffect(() => {
    if (activeIndex < 0) return;
    document
      .getElementById(optionId(listboxId, activeIndex))
      ?.scrollIntoView({ block: "nearest" });
  }, [activeIndex, listboxId]);

  const chooseMember = (member: OrgMember) => {
    onSelectMember(member);
    onOpenChange(false);
    inputRef.current?.focus();
  };

  const handleKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {
    if (event.key === "ArrowDown") {
      event.preventDefault();
      onOpenChange(true);
      if (suggestions.length === 0) return;
      setActiveIndex((prev) => {
        if (prev >= suggestions.length - 1) {
          if (search.hasMore && !search.isLoadingMore) search.loadMore();
          return suggestions.length - 1;
        }
        return prev + 1;
      });
      return;
    }

    if (event.key === "ArrowUp") {
      event.preventDefault();
      onOpenChange(true);
      if (suggestions.length === 0) return;
      setActiveIndex((prev) => (prev <= 0 ? suggestions.length - 1 : prev - 1));
      return;
    }

    if (event.key === "Enter") {
      if (open && activeMember) {
        event.preventDefault();
        chooseMember(activeMember);
        return;
      }
      if (value.trim()) {
        event.preventDefault();
        onSubmit();
      }
      return;
    }

    if (event.key === "Escape" && open) {
      event.preventDefault();
      event.stopPropagation();
      onOpenChange(false);
      setActiveIndex(-1);
    }
  };

  const handleScroll = (event: ReactUIEvent<HTMLDivElement>) => {
    const target = event.currentTarget;
    if (
      search.hasMore &&
      !search.isLoadingMore &&
      target.scrollTop + target.clientHeight >= target.scrollHeight - 24
    ) {
      search.loadMore();
    }
  };

  return (
    <Popover open={open} onOpenChange={onOpenChange}>
      <PopoverAnchor asChild>
        <div className="relative flex-1 min-w-0">
          <IconSearch
            aria-hidden
            size={15}
            strokeWidth={1.8}
            className="pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
          />
          <input
            ref={inputRef}
            type="email"
            role="combobox"
            aria-autocomplete="list"
            aria-expanded={open}
            aria-controls={open ? listboxId : undefined}
            aria-activedescendant={
              activeIndex >= 0 ? optionId(listboxId, activeIndex) : undefined
            }
            placeholder={placeholder}
            value={value}
            onChange={(event) => {
              onValueChange(event.target.value);
              onOpenChange(true);
            }}
            onFocus={() => onOpenChange(true)}
            onBlur={() => {
              setTimeout(() => {
                if (document.activeElement !== inputRef.current) {
                  onOpenChange(false);
                }
              }, 0);
            }}
            onKeyDown={handleKeyDown}
            autoComplete="off"
            className="h-9 w-full min-w-0 rounded-md border border-input bg-card ps-8 pe-8 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background"
          />
          {search.isLoading ? (
            <IconLoader2
              aria-hidden
              size={15}
              strokeWidth={1.8}
              className="pointer-events-none absolute end-2.5 top-1/2 -translate-y-1/2 animate-spin text-muted-foreground"
            />
          ) : null}
        </div>
      </PopoverAnchor>
      <PopoverContent
        align="start"
        sideOffset={4}
        data-agent-native-share-overlay=""
        onOpenAutoFocus={(event) => event.preventDefault()}
        className={cn(
          SHARE_NESTED_OVERLAY_Z,
          "w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg",
          SHARE_POPOVER_SURFACE,
        )}
      >
        <div
          id={listboxId}
          role="listbox"
          className="max-h-56 overflow-y-auto overflow-x-hidden"
          onScroll={handleScroll}
        >
          {suggestions.map((member, index) => {
            const active = index === activeIndex;
            return (
              <div
                key={member.email}
                id={optionId(listboxId, index)}
                role="option"
                aria-selected={active}
                onMouseDown={(event) => event.preventDefault()}
                onMouseEnter={() => setActiveIndex(index)}
                onClick={() => chooseMember(member)}
                className={cn(
                  "flex cursor-pointer select-none flex-col rounded-sm px-3 py-2 text-sm outline-none",
                  active
                    ? "bg-accent text-accent-foreground"
                    : "text-foreground hover:bg-accent hover:text-accent-foreground",
                )}
              >
                <span className="truncate font-medium">
                  {member.name?.trim() || member.email}
                </span>
                {member.name?.trim() ? (
                  <span className="truncate text-xs text-muted-foreground">
                    {member.email}
                  </span>
                ) : null}
              </div>
            );
          })}

          {search.isLoading && suggestions.length === 0 ? (
            <div className="px-3 py-3 text-sm text-muted-foreground">
              Searching...
            </div>
          ) : null}

          {search.error ? (
            <div className="px-3 py-3 text-sm text-muted-foreground">
              Could not load people.
            </div>
          ) : null}

          {!search.isLoading && !search.error && suggestions.length === 0 ? (
            <div className="px-3 py-3 text-sm text-muted-foreground">
              {value.trim() ? "No matches." : "No people found."}
            </div>
          ) : null}

          {search.isLoadingMore ? (
            <div className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground">
              <IconLoader2
                aria-hidden
                size={14}
                strokeWidth={1.8}
                className="animate-spin"
              />
              Loading...
            </div>
          ) : null}

          {search.hasMore && !search.isLoadingMore ? (
            <button
              type="button"
              onMouseDown={(event) => event.preventDefault()}
              onClick={search.loadMore}
              className="mt-1 flex w-full items-center justify-center rounded-sm px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground"
            >
              Load more
            </button>
          ) : null}
        </div>
      </PopoverContent>
    </Popover>
  );
}

function optionId(baseId: string, index: number): string {
  return `${baseId}-option-${index}`;
}

function CopyLinkField({
  value,
  label = "Share link",
  description,
}: {
  value: string;
  label?: string;
  description?: ReactNode;
}) {
  const [copied, setCopied] = useState(false);
  const resetRef = useRef<ReturnType<typeof setTimeout>>(undefined);

  useEffect(() => {
    return () => {
      if (resetRef.current) clearTimeout(resetRef.current);
    };
  }, []);

  const handleCopy = async () => {
    if (await writeClipboardText(value)) {
      setCopied(true);
      if (resetRef.current) clearTimeout(resetRef.current);
      resetRef.current = setTimeout(() => setCopied(false), 1400);
    } else {
      setCopied(false);
    }
  };

  return (
    <div className="mb-4">
      <div className="mb-2 text-sm font-semibold">{label}</div>
      {description ? (
        <div className="mb-2 text-xs text-muted-foreground">{description}</div>
      ) : null}
      <div className="flex min-w-0 items-center gap-2">
        <input
          readOnly
          value={value}
          className="h-9 min-w-0 flex-1 rounded-md border border-input bg-card px-3 text-sm text-muted-foreground outline-none"
          onFocus={(event) => event.currentTarget.select()}
        />
        <button
          type="button"
          onClick={handleCopy}
          className="inline-flex h-9 shrink-0 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm font-medium text-foreground hover:bg-accent"
        >
          {copied ? <IconCheck size={15} /> : <IconCopy size={15} />}
          {copied ? "Copied" : "Copy"}
        </button>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Radix Select wrappers styled like shadcn Select (no native <select> anywhere)
// ---------------------------------------------------------------------------

const selectContentClass = `${SHARE_NESTED_OVERLAY_Z} min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0`;
const selectItemClass =
  "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm py-2 ps-8 pe-3 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";

interface ShadSelectItemProps {
  value: string;
  label: string;
  description?: string;
}

function SelectItems({ items }: { items: ShadSelectItemProps[] }) {
  return (
    <>
      {items.map((it) => (
        <Select.Item
          key={it.value}
          value={it.value}
          className={selectItemClass}
        >
          <span className="absolute start-2 top-2 flex h-4 w-4 items-center justify-center">
            <Select.ItemIndicator>
              <IconCheck size={14} />
            </Select.ItemIndicator>
          </span>
          <span className="flex flex-col">
            <Select.ItemText>{it.label}</Select.ItemText>
            {it.description ? (
              <span className="text-xs text-muted-foreground">
                {it.description}
              </span>
            ) : null}
          </span>
        </Select.Item>
      ))}
    </>
  );
}

function RoleSelect(props: {
  value: Role;
  onChange: (v: Role) => void;
  disabled?: boolean;
  /** When true, render as inline text + chevron (no border / bg) — matches
   *  the per-person role picker in Google Docs. */
  plain?: boolean;
}) {
  const current =
    ROLE_OPTIONS.find((o) => o.value === props.value) ?? ROLE_OPTIONS[0];
  return (
    <Select.Root
      value={props.value}
      onValueChange={(v) => props.onChange(v as Role)}
      disabled={props.disabled}
    >
      <Select.Trigger
        className={
          props.plain
            ? cn(
                BUTTON_BASE,
                "h-7 px-2 bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground",
              )
            : cn(
                BUTTON_BASE,
                "h-9 px-3 border border-input bg-card hover:bg-accent hover:text-accent-foreground",
              )
        }
        aria-label="Role"
      >
        <Select.Value>{current.label}</Select.Value>
        <Select.Icon>
          <IconChevronDown size={14} />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content
          data-agent-native-share-overlay=""
          className={selectContentClass}
          position="popper"
          sideOffset={4}
        >
          <Select.Viewport>
            <SelectItems items={ROLE_OPTIONS} />
          </Select.Viewport>
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  );
}

function VisibilitySelect(props: {
  value: Visibility;
  onChange: (v: Visibility) => void;
  disabled?: boolean;
  visibilityCopy?: ShareButtonProps["visibilityCopy"];
  /** When false, the "Private" option is omitted unless currently selected. */
  allowPrivate?: boolean;
  /** When false, the "Public" option is omitted. Default: true. */
  allowPublic?: boolean;
}) {
  const allowPrivate = props.allowPrivate !== false;
  const allowPublic = props.allowPublic !== false;
  const current = visibilityMeta(props.value, props.visibilityCopy);
  const options = (Object.keys(VIS_META) as Visibility[]).filter((k) => {
    if (k === props.value) return true;
    if (k === "private" && !allowPrivate) return false;
    if (k === "public" && !allowPublic) return false;
    return true;
  });
  return (
    <Select.Root
      value={props.value}
      onValueChange={(v) => props.onChange(v as Visibility)}
      disabled={props.disabled}
    >
      <Select.Trigger
        className={cn(
          BUTTON_BASE,
          "h-7 px-1 -ms-1 bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground",
        )}
        aria-label="General access"
      >
        <Select.Value>{current.label}</Select.Value>
        <Select.Icon>
          <IconChevronDown size={14} />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content
          data-agent-native-share-overlay=""
          className={selectContentClass}
          position="popper"
          sideOffset={4}
        >
          <Select.Viewport>
            <SelectItems
              items={options.map((k) => ({
                value: k,
                label: visibilityMeta(k, props.visibilityCopy).label,
                description: visibilityMeta(k, props.visibilityCopy)
                  .description,
              }))}
            />
          </Select.Viewport>
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  );
}

function Avatar({ label, org }: { label: string; org?: boolean }) {
  return (
    <span
      aria-hidden
      className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted text-[11px] font-semibold text-muted-foreground"
    >
      {org ? <IconUsersGroup size={14} strokeWidth={1.75} /> : initials(label)}
    </span>
  );
}

function keyOf(s: Share): string {
  return `${s.principalType}:${s.principalId}`;
}

function cap(s: string): string {
  return s.charAt(0).toUpperCase() + s.slice(1);
}
function initials(s: string): string {
  const name = s.split("@")[0] ?? s;
  return (name[0] ?? "?").toUpperCase();
}

function principalLabel(share: Share, members: OrgMember[]): string {
  const serverLabel = share.displayName?.trim();
  if (serverLabel) return serverLabel;
  if (share.principalType === "org") return "Organization";
  return displayName(share.principalId, members);
}

function displayName(emailOrId: string, members: OrgMember[]): string {
  const normalized = emailOrId.trim().toLowerCase();
  const match = members.find((m) => m.email.toLowerCase() === normalized);
  if (match?.name && match.name.trim()) return match.name;
  return normalized.includes("@") ? emailOrId : "Unknown person";
}
