"use client"

import * as React from "react"
import { useTranslation } from "react-i18next"
import { RiDownloadLine, RiEyeLine, RiPriceTag3Line, RiFileList2Line, RiLockLine, RiCloseLine } from "@remixicon/react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Switch } from "@/components/ui/switch"
import { Spinner } from "@/components/ui/spinner"
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription } from "@/components/ui/drawer"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Item, ItemContent } from "@/components/ui/item"
import { Field, FieldContent, FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { CopyInput } from "@/components/copy-input"
import { DateTimePicker } from "@/components/datetime-picker"
import { useObject } from "@/hooks/use-object"
import { usePermissions } from "@/hooks/use-permissions"
import { useMessage } from "@/lib/feedback/message"
import { useDialog } from "@/lib/feedback/dialog"
import { exportFile } from "@/lib/export-file"
import { getContentType } from "@/lib/mime-types"
import { normalizeDateToIso } from "@/lib/safe-date"
import {
  getDefaultObjectRetentionDate,
  getMinimumObjectRetentionDate,
  isObjectLegalHoldEnabled,
  isObjectRetentionDateInFuture,
  shouldShowObjectRetentionAction,
  toObjectRetentionRequestValue,
} from "@/lib/object-lock.js"
import { ObjectVersions } from "@/components/object/versions"
import { GetObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
import { useS3 } from "@/contexts/s3-context"
import { CopyButton } from "@/components/copy-button"

const ONE_WEEK_SECONDS = 7 * 24 * 60 * 60

interface ObjectInfoProps {
  bucketName: string
  objectKey: string | null
  open: boolean
  onOpenChange: (open: boolean) => void
  onRefresh?: () => void
  onPreview: (params: { key?: string; data?: Record<string, unknown> }) => void
}

export function ObjectInfo({ bucketName, objectKey, open, onOpenChange, onPreview }: ObjectInfoProps) {
  const { t } = useTranslation()
  const message = useMessage()
  const dialog = useDialog()
  const objectApi = useObject(bucketName)
  const { canCapability } = usePermissions()
  const client = useS3()

  const [object, setObject] = React.useState<Record<string, unknown> | null>(null)
  const [tags, setTags] = React.useState<Array<{ Key: string; Value: string }>>([])
  const [lockStatus, setLockStatus] = React.useState(false)
  const [retention, setRetention] = React.useState("")
  const [retainUntilDate, setRetainUntilDate] = React.useState("")
  const [retentionMode, setRetentionMode] = React.useState<"COMPLIANCE" | "GOVERNANCE">("GOVERNANCE")
  const [minRetentionDate, setMinRetentionDate] = React.useState(() => getMinimumObjectRetentionDate())
  const [signedUrl, setSignedUrl] = React.useState("")
  const [showTagView, setShowTagView] = React.useState(false)
  const [showRetentionView, setShowRetentionView] = React.useState(false)
  const [showVersions, setShowVersions] = React.useState(false)
  const [tagFormValue, setTagFormValue] = React.useState({
    Key: "",
    Value: "",
  })
  const [expirationDays, setExpirationDays] = React.useState(0)
  const [expirationHours, setExpirationHours] = React.useState(2)
  const [expirationMinutes, setExpirationMinutes] = React.useState(0)
  const [expirationError, setExpirationError] = React.useState("")
  const [totalExpirationSeconds, setTotalExpirationSeconds] = React.useState(0)
  const [isExpirationValid, setIsExpirationValid] = React.useState(false)
  const [isGeneratingUrl, setIsGeneratingUrl] = React.useState(false)
  const [isUpdatingRetention, setIsUpdatingRetention] = React.useState(false)
  const retentionDialogContentRef = React.useRef<HTMLDivElement>(null)
  const resolvedObjectKey = React.useMemo(() => String(object?.Key ?? objectKey ?? ""), [object?.Key, objectKey])
  const objectPermissionContext = React.useMemo(
    () => ({
      bucket: bucketName,
      objectKey: resolvedObjectKey || undefined,
    }),
    [bucketName, resolvedObjectKey],
  )
  const canDownloadObject = canCapability("objects.download", objectPermissionContext)
  const canPreviewObject = canCapability("objects.preview", objectPermissionContext)
  const canViewObjectTags = canCapability("objects.tag.view", objectPermissionContext)
  const canEditObjectTags = canCapability("objects.tag.edit", objectPermissionContext)
  const canViewVersions = canCapability("objects.version.view", objectPermissionContext)
  const canEditLegalHold = canCapability("objects.legalHold.edit", objectPermissionContext)
  const canEditRetention = canCapability("objects.retention.edit", objectPermissionContext)
  const canShareObject = canCapability("objects.share", objectPermissionContext)
  const showRetentionAction = shouldShowObjectRetentionAction({
    canEditRetention,
    legalHoldEnabled: lockStatus,
  })

  const formatDuration = (seconds: number) => {
    if (seconds === 0) return ""
    const days = Math.floor(seconds / (24 * 60 * 60))
    const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60))
    const minutes = Math.floor((seconds % (60 * 60)) / 60)
    const parts: string[] = []
    if (days > 0) parts.push(`${days} ${t("Days")}`)
    if (hours > 0) parts.push(`${hours} ${t("Hours")}`)
    if (minutes > 0) parts.push(`${minutes} ${t("Minutes")}`)
    return parts.join(" ")
  }

  const validateExpiration = React.useCallback((): boolean => {
    setExpirationError("")
    const days = Number(expirationDays) || 0
    const hours = Number(expirationHours) || 0
    const minutes = Number(expirationMinutes) || 0

    if (isNaN(days) || isNaN(hours) || isNaN(minutes)) return false
    if (minutes < 0 || minutes > 59) {
      setExpirationError(t("Minutes must be between 0 and 59"))
      return false
    }
    if (hours < 0) {
      setExpirationError(t("Hours must be between 0 and 23"))
      return false
    }
    if (days > 0 && hours > 23) {
      setExpirationError(t("Hours must be between 0 and 23"))
      return false
    }
    if (days === 0 && hours > 24) {
      setExpirationError(t("Hours must be between 0 and 24 when days is 0"))
      return false
    }
    if (days < 0 || days > 7) {
      setExpirationError(t("Days must be between 0 and 7"))
      return false
    }

    let finalDays = days
    let finalHours = hours
    if (hours === 24 && days === 0) {
      finalDays = 1
      finalHours = 0
    }
    const totalSeconds = finalDays * 24 * 60 * 60 + finalHours * 60 * 60 + minutes * 60
    if (totalSeconds > ONE_WEEK_SECONDS) {
      setExpirationError(t("Total duration cannot exceed 7 days"))
      return false
    }
    if (totalSeconds === 0) {
      setTotalExpirationSeconds(0)
      return false
    }

    setTotalExpirationSeconds(totalSeconds)
    return true
  }, [expirationDays, expirationHours, expirationMinutes, t])

  React.useEffect(() => {
    setIsExpirationValid(validateExpiration())
  }, [expirationDays, expirationHours, expirationMinutes, validateExpiration])

  const loadObjectInfo = React.useCallback(
    async (key: string) => {
      const info = await objectApi.getObjectInfo(key)
      setObject(info as Record<string, unknown>)
      setLockStatus(isObjectLegalHoldEnabled(info))
      setExpirationDays(0)
      setExpirationHours(2)
      setExpirationMinutes(0)
      setSignedUrl("")
      setExpirationError("")
      setTotalExpirationSeconds(2 * 60 * 60)
      setIsExpirationValid(true)
      setIsGeneratingUrl(false)
    },
    [objectApi],
  )

  const fetchTags = React.useCallback(
    async (key: string) => {
      try {
        const response = await objectApi.getObjectTags(key)
        setTags(response)
      } catch {
        setTags([])
      }
    },
    [objectApi],
  )

  const fetchRetention = React.useCallback(
    async (key: string) => {
      try {
        const response = await objectApi.getObjectRetention(key)
        setRetention(response.Mode ?? "")
        setRetainUntilDate(response.RetainUntilDate ?? "")
        setRetentionMode(response.Mode === "COMPLIANCE" ? "COMPLIANCE" : "GOVERNANCE")
      } catch {
        setRetention("")
        setRetainUntilDate("")
      }
    },
    [objectApi],
  )

  const fetchLegalHold = React.useCallback(
    async (key: string) => {
      try {
        const response = await objectApi.getObjectLegalHold(key)
        if (response.Status) {
          setLockStatus(isObjectLegalHoldEnabled(response))
        }
      } catch {
        // Keep the HeadObject fallback state when a server does not support the dedicated legal hold API.
      }
    },
    [objectApi],
  )

  const loadObjectInfoRef = React.useRef(loadObjectInfo)
  const fetchTagsRef = React.useRef(fetchTags)
  const fetchRetentionRef = React.useRef(fetchRetention)
  const fetchLegalHoldRef = React.useRef(fetchLegalHold)

  React.useEffect(() => {
    loadObjectInfoRef.current = loadObjectInfo
    fetchTagsRef.current = fetchTags
    fetchRetentionRef.current = fetchRetention
    fetchLegalHoldRef.current = fetchLegalHold
  }, [loadObjectInfo, fetchTags, fetchRetention, fetchLegalHold])

  React.useEffect(() => {
    if (open && objectKey) {
      const key = objectKey
      loadObjectInfoRef
        .current(key)
        .then(() =>
          Promise.all([fetchTagsRef.current(key), fetchRetentionRef.current(key), fetchLegalHoldRef.current(key)]),
        )
        .catch(() => {
          message.error(t("Failed to fetch object info"))
          setObject(null)
        })
    } else {
      setObject(null)
    }
  }, [open, objectKey, message, t])

  const download = async () => {
    if (!canDownloadObject || !object?.Key) return
    try {
      const url = await objectApi.getSignedUrl(object.Key as string)
      const response = await fetch(url)
      const filename = (object.Key as string).split("/").pop() ?? ""
      const headers: Record<string, string> = {
        "content-type": getContentType(response.headers, filename),
        filename: response.headers.get("content-disposition")?.split("filename=")[1] ?? "",
      }
      const blob = await response.blob()
      exportFile({ headers, data: blob }, filename)
    } catch (err) {
      message.error((err as Error)?.message ?? t("Download Failed"))
    }
  }

  const handlePreviewVersion = async (versionId: string) => {
    if (!resolvedObjectKey) return
    try {
      const key = resolvedObjectKey
      const version = versionId === "00000000-0000-0000-0000-000000000000" ? undefined : versionId
      const [head, signed] = await Promise.all([
        client.send(
          new HeadObjectCommand({
            Bucket: bucketName,
            Key: key,
            VersionId: version,
          }),
        ),
        getSignedUrl(
          client,
          new GetObjectCommand({
            Bucket: bucketName,
            Key: key,
            VersionId: version,
          }),
          { expiresIn: 3600 },
        ),
      ])
      const data = { ...head, SignedUrl: signed }
      setShowVersions(false)
      onPreview({ key, data })
    } catch (err) {
      message.error((err as Error)?.message ?? t("Failed to fetch versions"))
    }
  }
  const toggleLegalHold = async () => {
    if (!canEditLegalHold || !object?.Key) return
    try {
      await objectApi.setLegalHold(object.Key as string, !lockStatus)
      setLockStatus(!lockStatus)
      message.success(t("Update Success"))
    } catch (err) {
      message.error((err as Error)?.message ?? t("Update Failed"))
    }
  }

  const generateTemporaryUrl = async () => {
    if (!canShareObject || !object?.Key || isGeneratingUrl) return
    if (!validateExpiration()) {
      message.error(expirationError || t("Please enter a valid expiration time"))
      return
    }
    setIsGeneratingUrl(true)
    try {
      const url = await objectApi.getSignedUrl(object.Key as string, totalExpirationSeconds)
      setSignedUrl(url)
      message.success(t("URL generated successfully"))
    } catch (err) {
      message.error((err as Error)?.message ?? t("Failed to generate URL"))
    } finally {
      setIsGeneratingUrl(false)
    }
  }

  const submitTagForm = async () => {
    if (!canEditObjectTags) return
    if (!resolvedObjectKey) {
      message.error(t("Failed to fetch object info"))
      return
    }

    const nextTag = {
      Key: tagFormValue.Key.trim(),
      Value: tagFormValue.Value.trim(),
    }
    if (!nextTag.Key || !nextTag.Value) {
      message.error(t("Please fill in the correct format"))
      return
    }
    try {
      const nextTags = [...tags, nextTag]
      await objectApi.putObjectTags(resolvedObjectKey, nextTags)
      setTags(nextTags)
      setTagFormValue({ Key: "", Value: "" })
      message.success(t("Create Success"))
    } catch (err) {
      message.error((err as Error)?.message ?? t("Create Failed"))
    }
  }

  const confirmDeleteTag = (tagKey: string) => {
    dialog.warning({
      title: t("Delete Tag"),
      content: t("Are you sure you want to delete this tag?"),
      positiveText: t("Confirm"),
      negativeText: t("Cancel"),
      onPositiveClick: () => removeTag(tagKey),
    })
  }

  const removeTag = async (tagKey: string) => {
    if (!canEditObjectTags || !resolvedObjectKey) return
    try {
      const nextTags = tags.filter((tag) => tag.Key !== tagKey)
      await objectApi.putObjectTags(resolvedObjectKey, nextTags)
      setTags(nextTags)
      message.success(t("Delete Success"))
    } catch (err) {
      message.error((err as Error)?.message ?? t("Delete Failed"))
    }
  }

  const submitRetention = async () => {
    if (isUpdatingRetention) return
    if (!resolvedObjectKey) {
      message.error(t("Failed to fetch object info"))
      return
    }
    if (!canEditRetention) {
      message.error(t("Update Failed"))
      return
    }
    if (!isObjectRetentionDateInFuture(retainUntilDate)) {
      message.error(t("The retain until date must be in the future"))
      return
    }

    setIsUpdatingRetention(true)
    try {
      await objectApi.putObjectRetention(resolvedObjectKey, {
        Mode: retentionMode,
        RetainUntilDate: toObjectRetentionRequestValue(retainUntilDate),
      })
      message.success(t("Update Success"))
      setShowRetentionView(false)
      fetchRetention(resolvedObjectKey)
    } catch (err) {
      message.error((err as Error)?.message ?? t("Update Failed"))
    } finally {
      setIsUpdatingRetention(false)
    }
  }

  const resetRetention = async () => {
    if (isUpdatingRetention) return
    if (!resolvedObjectKey) {
      message.error(t("Failed to fetch object info"))
      return
    }
    if (!canEditRetention) {
      message.error(t("Update Failed"))
      return
    }

    setIsUpdatingRetention(true)
    try {
      await objectApi.putObjectRetention(resolvedObjectKey, {
        Mode: "GOVERNANCE",
      })
      message.success(t("Update Success"))
      fetchRetention(resolvedObjectKey)
    } catch (err) {
      message.error((err as Error)?.message ?? t("Update Failed"))
    } finally {
      setIsUpdatingRetention(false)
    }
  }

  const lastModified = normalizeDateToIso((object?.LastModified as string | Date | undefined) ?? undefined)

  return (
    <>
      <Drawer open={open} onOpenChange={onOpenChange} swipeDirection="right">
        <DrawerContent className="overflow-y-auto overflow-x-hidden data-[swipe-direction=right]:w-[92vw] data-[swipe-direction=right]:sm:max-w-2xl">
          <DrawerHeader>
            <DrawerTitle>{t("Object Details")}</DrawerTitle>
            <DrawerDescription className="break-all">{resolvedObjectKey || bucketName}</DrawerDescription>
          </DrawerHeader>
          <div className="min-w-0 space-y-4 p-4 overflow-hidden">
            <div className="flex flex-wrap items-center gap-2">
              {canDownloadObject ? (
                <Button variant="outline" size="sm" onClick={download}>
                  <RiDownloadLine className="size-4" aria-hidden />
                  {t("Download")}
                </Button>
              ) : null}
              {canPreviewObject ? (
                <Button variant="outline" size="sm" onClick={() => onPreview({ key: String(object?.Key ?? "") })}>
                  <RiEyeLine className="size-4" aria-hidden />
                  {t("Preview")}
                </Button>
              ) : null}
              {canViewObjectTags || canEditObjectTags ? (
                <Button variant="outline" size="sm" onClick={() => setShowTagView(true)}>
                  <RiPriceTag3Line className="size-4" aria-hidden />
                  {t("Set Tags")}
                </Button>
              ) : null}
              {canViewVersions ? (
                <Button variant="outline" size="sm" onClick={() => setShowVersions(true)}>
                  <RiFileList2Line className="size-4" aria-hidden />
                  {t("Versions")}
                </Button>
              ) : null}
              {showRetentionAction ? (
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => {
                    setMinRetentionDate(getMinimumObjectRetentionDate())
                    if (!retainUntilDate || !isObjectRetentionDateInFuture(retainUntilDate)) {
                      setRetainUntilDate(getDefaultObjectRetentionDate())
                    }
                    setShowRetentionView(true)
                  }}
                >
                  <RiLockLine className="size-4" aria-hidden />
                  {t("Retention")}
                </Button>
              ) : null}
            </div>

            <Item variant="outline" className="flex-col items-stretch gap-4">
              <Tabs defaultValue="info" className="w-full">
                <TabsList className="mb-2 inline-flex h-9 items-center justify-center rounded-none bg-muted p-1 text-muted-foreground">
                  <TabsTrigger value="info" className="px-4">
                    {t("Info")}
                  </TabsTrigger>
                  <TabsTrigger value="metadata" className="px-4">
                    {t("Metadata")}
                  </TabsTrigger>
                </TabsList>
                <TabsContent
                  value="info"
                  className="space-y-4 outline-none focus-visible:ring-1 focus-visible:ring-ring/50"
                >
                  <ItemContent className="min-w-0 space-y-3 text-sm overflow-hidden">
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <span className="font-medium text-muted-foreground">{t("Object Name")}</span>
                      <span className="flex-1 truncate text-end" title={String(object?.Key ?? "")}>
                        {String(object?.Key ?? "")}
                      </span>
                      <CopyButton value={String(object?.Key ?? "")} iconOnly />
                    </div>
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <span className="font-medium text-muted-foreground">{t("Object Size")}</span>
                      <span className="max-w-[60%] truncate text-end" title={String(object?.ContentLength ?? "-")}>
                        {String(object?.ContentLength ?? "-")}
                      </span>
                    </div>
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <span className="font-medium text-muted-foreground">{t("Object Type")}</span>
                      <span className="max-w-[60%] truncate text-end" title={String(object?.ContentType ?? "")}>
                        {String(object?.ContentType ?? "-")}
                      </span>
                    </div>
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <span className="font-medium text-muted-foreground">ETag</span>
                      <span className="max-w-[60%] truncate text-end" title={String(object?.ETag ?? "-")}>
                        {String(object?.ETag ?? "-")}
                      </span>
                    </div>
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <span className="font-medium text-muted-foreground">{t("Last Modified Time")}</span>
                      <span className="max-w-[60%] truncate text-end" title={lastModified || "-"}>
                        {lastModified || "-"}
                      </span>
                    </div>
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <label htmlFor="object-legal-hold" className="font-medium text-muted-foreground">
                        {t("Legal Hold")}
                      </label>
                      <Switch
                        id="object-legal-hold"
                        checked={lockStatus}
                        onCheckedChange={toggleLegalHold}
                        disabled={!canEditLegalHold}
                      />
                    </div>
                    <div className="flex flex-col gap-2">
                      <span className="font-medium text-muted-foreground">
                        {t("Retention")}
                        {t("Policy")}
                      </span>
                      <div className="flex flex-col gap-1">
                        <span className="truncate" title={retention}>
                          {retention}
                        </span>
                        {retainUntilDate && (
                          <span className="text-xs text-muted-foreground truncate" title={retainUntilDate}>
                            {retainUntilDate}
                          </span>
                        )}
                      </div>
                    </div>
                  </ItemContent>
                </TabsContent>
                <TabsContent
                  value="metadata"
                  className="space-y-4 outline-none focus-visible:ring-1 focus-visible:ring-ring/50"
                >
                  <ItemContent className="min-w-0 space-y-3 text-sm overflow-hidden">
                    <div className="flex items-center justify-between gap-3 min-w-0">
                      <span className="font-medium text-muted-foreground">{t("Content Type")}</span>
                      <span className="max-w-[60%] truncate text-end" title={String(object?.ContentType ?? "")}>
                        {String(object?.ContentType ?? "")}
                      </span>
                    </div>
                    {object &&
                      Object.entries(object?.Metadata ?? {}).map(([key, value]) => (
                        <React.Fragment key={key}>
                          <div className="flex items-center justify-between gap-3 min-w-0">
                            <span className="font-medium text-muted-foreground">{t(key)}</span>
                            <span className="max-w-[60%] truncate text-end" title={String(key)}>
                              {String(value)}
                            </span>
                          </div>
                        </React.Fragment>
                      ))}
                  </ItemContent>
                </TabsContent>
              </Tabs>
              <ItemContent className="min-w-0 space-y-3 text-sm overflow-hidden">
                <div className="border-t pt-3 flex flex-col gap-3 min-w-0">
                  <span className="font-medium text-muted-foreground">{t("Temporary URL Expiration")}</span>
                  <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
                    <div className="flex items-center gap-1.5 shrink-0">
                      <Input
                        name="temporary-url-expiration-days"
                        type="number"
                        inputMode="numeric"
                        min={0}
                        max={7}
                        aria-label={t("Days")}
                        placeholder={t("Days")}
                        className="w-14 shrink-0"
                        value={expirationDays}
                        onChange={(e) => setExpirationDays(Number(e.target.value) || 0)}
                      />
                      <span className="text-sm text-muted-foreground whitespace-nowrap">{t("Days")}</span>
                    </div>
                    <div className="flex items-center gap-1.5 shrink-0">
                      <Input
                        name="temporary-url-expiration-hours"
                        type="number"
                        inputMode="numeric"
                        min={0}
                        max={expirationDays > 0 ? 23 : 24}
                        aria-label={t("Hours")}
                        placeholder={t("Hours")}
                        className="w-14 shrink-0"
                        value={expirationHours}
                        onChange={(e) => setExpirationHours(Number(e.target.value) || 0)}
                      />
                      <span className="text-sm text-muted-foreground whitespace-nowrap">{t("Hours")}</span>
                    </div>
                    <div className="flex items-center gap-1.5 shrink-0">
                      <Input
                        name="temporary-url-expiration-minutes"
                        type="number"
                        inputMode="numeric"
                        min={0}
                        max={59}
                        aria-label={t("Minutes")}
                        placeholder={t("Minutes")}
                        className="w-14 shrink-0"
                        value={expirationMinutes}
                        onChange={(e) => setExpirationMinutes(Number(e.target.value) || 0)}
                      />
                      <span className="text-sm text-muted-foreground whitespace-nowrap">{t("Minutes")}</span>
                    </div>
                    <Button
                      variant="default"
                      size="sm"
                      className="shrink-0"
                      disabled={!canShareObject || !object?.Key || !isExpirationValid || isGeneratingUrl}
                      onClick={generateTemporaryUrl}
                    >
                      {isGeneratingUrl ? <Spinner className="size-4" /> : null}
                      <span>{t("Generate URL")}</span>
                    </Button>
                  </div>
                  {expirationError && <div className="text-xs text-destructive">{expirationError}</div>}
                  {totalExpirationSeconds > 0 && (
                    <div className="text-xs text-muted-foreground">
                      {t("Total Duration")}: {formatDuration(totalExpirationSeconds)}
                    </div>
                  )}
                </div>
                <div className="flex flex-col gap-2 min-w-0 w-full">
                  <CopyInput value={signedUrl} readonly copyIcon className="min-w-0 flex-1" />
                  {signedUrl && (
                    <div className="mt-1 text-xs text-muted-foreground">
                      {t("This link will expire when your session ends or at the specified time.")}
                    </div>
                  )}
                </div>
              </ItemContent>
            </Item>
          </div>
        </DrawerContent>
      </Drawer>

      <Dialog open={showTagView} onOpenChange={setShowTagView} disablePointerDismissal>
        <DialogContent className="sm:max-w-lg">
          <DialogHeader>
            <DialogTitle>{t("Set Tags")}</DialogTitle>
          </DialogHeader>
          <div className="space-y-4">
            <div className="flex flex-wrap gap-2">
              {tags.map((tag) => (
                <Badge
                  key={tag.Key}
                  variant="secondary"
                  className="h-auto max-w-full justify-start gap-1 pe-1 whitespace-normal"
                >
                  <span className="min-w-0 break-all" title={`${tag.Key}: ${tag.Value}`}>
                    {tag.Key}: {tag.Value}
                  </span>
                  {canEditObjectTags ? (
                    <button
                      type="button"
                      aria-label={t("Delete Tag")}
                      className="inline-flex size-6 shrink-0 items-center justify-center transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring/50"
                      onClick={() => confirmDeleteTag(tag.Key)}
                    >
                      <RiCloseLine className="size-4" aria-hidden />
                    </button>
                  ) : null}
                </Badge>
              ))}
            </div>
            <form
              className="space-y-4"
              onSubmit={(e) => {
                e.preventDefault()
                void submitTagForm()
              }}
            >
              <div className="grid gap-4 sm:grid-cols-2">
                <Field>
                  <FieldLabel htmlFor="object-tag-key">{t("Tag Key")}</FieldLabel>
                  <FieldContent>
                    <Input
                      id="object-tag-key"
                      name="object-tag-key"
                      value={tagFormValue.Key}
                      onChange={(e) => setTagFormValue((v) => ({ ...v, Key: e.target.value }))}
                      placeholder={t("Tag Key Placeholder")}
                      disabled={!canEditObjectTags}
                    />
                  </FieldContent>
                </Field>
                <Field>
                  <FieldLabel htmlFor="object-tag-value">{t("Tag Value")}</FieldLabel>
                  <FieldContent>
                    <Input
                      id="object-tag-value"
                      name="object-tag-value"
                      value={tagFormValue.Value}
                      onChange={(e) =>
                        setTagFormValue((v) => ({
                          ...v,
                          Value: e.target.value,
                        }))
                      }
                      placeholder={t("Tag Value Placeholder")}
                      disabled={!canEditObjectTags}
                    />
                  </FieldContent>
                </Field>
              </div>
              <div className="flex justify-end">
                <Button
                  type="button"
                  variant="default"
                  onClick={() => void submitTagForm()}
                  disabled={!canEditObjectTags || !resolvedObjectKey}
                >
                  {t("Add")}
                </Button>
              </div>
            </form>
          </div>
        </DialogContent>
      </Dialog>

      <Dialog open={showRetentionView} onOpenChange={setShowRetentionView} disablePointerDismissal>
        <DialogContent className="sm:max-w-lg">
          <div ref={retentionDialogContentRef} className="contents">
            <DialogHeader>
              <DialogTitle>{t("Retention")}</DialogTitle>
            </DialogHeader>
            <div className="flex flex-col gap-3">
              <Field>
                <FieldLabel>{t("Retention Mode")}</FieldLabel>
                <FieldContent>
                  <RadioGroup
                    value={retentionMode}
                    onValueChange={(v) => setRetentionMode(v as "COMPLIANCE" | "GOVERNANCE")}
                    className="grid gap-2 sm:grid-cols-2"
                  >
                    {[
                      { label: t("COMPLIANCE"), value: "COMPLIANCE" },
                      { label: t("GOVERNANCE"), value: "GOVERNANCE" },
                    ].map((opt) => (
                      <label
                        key={opt.value}
                        htmlFor={`object-retention-mode-${opt.value.toLowerCase()}`}
                        className="flex items-start gap-3 border border-border/50 p-3 cursor-pointer"
                      >
                        <RadioGroupItem
                          id={`object-retention-mode-${opt.value.toLowerCase()}`}
                          value={opt.value}
                          className="mt-0.5"
                        />
                        <span className="text-sm font-medium">{opt.label}</span>
                      </label>
                    ))}
                  </RadioGroup>
                </FieldContent>
              </Field>
              <Field>
                <FieldLabel>{t("Retention RetainUntilDate")}</FieldLabel>
                <FieldContent>
                  <DateTimePicker
                    value={retainUntilDate}
                    onChange={(value) => setRetainUntilDate(value ?? "")}
                    placeholder={t("Retention RetainUntilDate")}
                    min={minRetentionDate}
                    disabled={!canEditRetention || isUpdatingRetention}
                    portalContainer={retentionDialogContentRef}
                  />
                </FieldContent>
              </Field>
              <div className="flex justify-end gap-2">
                <Button
                  type="button"
                  variant="secondary"
                  onClick={resetRetention}
                  disabled={!canEditRetention || isUpdatingRetention}
                >
                  {t("Reset")}
                </Button>
                <Button
                  type="button"
                  variant="default"
                  onClick={() => void submitRetention()}
                  disabled={!canEditRetention || isUpdatingRetention}
                >
                  {isUpdatingRetention ? <Spinner className="size-4" /> : null}
                  {t("Confirm")}
                </Button>
                <Button
                  type="button"
                  variant="outline"
                  onClick={() => setShowRetentionView(false)}
                  disabled={isUpdatingRetention}
                >
                  {t("Cancel")}
                </Button>
              </div>
            </div>
          </div>
        </DialogContent>
      </Dialog>

      <ObjectVersions
        bucketName={bucketName}
        objectKey={resolvedObjectKey}
        visible={showVersions}
        onClose={() => setShowVersions(false)}
        onPreview={handlePreviewVersion}
        onRefreshParent={() => objectKey && loadObjectInfo(objectKey)}
      />
    </>
  )
}
