oxios 1.10.1

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { FolderOpen } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea'
import { useMounts } from '@/hooks/use-mounts'
import { useUpdateProject } from '@/hooks/use-projects'
import type { Project } from '@/types'

interface EditProjectDialogProps {
  project: Project | null
  open: boolean
  onOpenChange: (open: boolean) => void
  onSuccess?: () => void
}

import { ICON_OPTIONS } from './create-project-dialog'

export function EditProjectDialog({
  project,
  open,
  onOpenChange,
  onSuccess,
}: EditProjectDialogProps) {
  const { t } = useTranslation()
  const update = useUpdateProject()

  const [name, setName] = useState('')
  const [icon, setIcon] = useState('package')
  const [description, setDescription] = useState('')
  const [tags, setTags] = useState('')
  const [paths, setPaths] = useState('')
  const [memoryVisible, setMemoryVisible] = useState(true)
  // RFC-025: mount_ids + instructions
  const [mountIds, setMountIds] = useState<string[]>([])
  const [instructions, setInstructions] = useState('')
  const { data: mountsData } = useMounts()
  const availableMounts = mountsData?.items ?? []

  // Sync state when the project prop or open state changes.
  useEffect(() => {
    if (project && open) {
      setName(project.name)
      setIcon(project.emoji ?? 'package')
      setDescription(project.description ?? '')
      setTags((project.tags ?? []).join(', '))
      setPaths((project.paths ?? []).join('\n'))
      setMemoryVisible(project.memory_visible ?? true)
      setMountIds(project.mount_ids ?? [])
      setInstructions(project.instructions ?? '')
    }
  }, [project?.id, open])

  const handleSubmit = () => {
    if (!project || !name.trim()) return

    update.mutate(
      {
        id: project.id,
        name: name.trim(),
        description: description.trim() || undefined,
        tags: tags
          .split(',')
          .map((t) => t.trim())
          .filter(Boolean),
        paths: paths
          .split('\n')
          .map((p) => p.trim())
          .filter(Boolean),
        emoji: icon,
        memory_visible: memoryVisible,
        mount_ids: mountIds,
        instructions: instructions.trim() || undefined,
      },
      {
        onSuccess: () => {
          toast(t('projects.updateSuccess', 'Project updated'))
          onOpenChange(false)
          onSuccess?.()
        },
        onError: (err) => {
          toast.error(t('projects.updateError', `Failed to update: ${err}`))
        },
      },
    )
  }

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>{t('projects.editTitle', 'Edit Project')}</DialogTitle>
          <DialogDescription>
            {project?.name
              ? t('projects.editDesc', 'Update "{{name}}"', { name: project.name })
              : ''}
          </DialogDescription>
        </DialogHeader>

        <div className="space-y-4 py-2">
          <div className="space-y-1">
            <label className="text-sm font-medium">{t('projects.name', 'Name')}</label>
            <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="oxios" />
          </div>

          <div className="space-y-1">
            <label className="text-sm font-medium">{t('projects.icon', 'Icon')}</label>
            <div className="flex flex-wrap gap-1">
              {ICON_OPTIONS.map((opt) => (
                <button
                  key={opt.name}
                  type="button"
                  onClick={() => setIcon(opt.name)}
                  className={`w-8 h-8 rounded flex items-center justify-center border transition-colors ${
                    icon === opt.name
                      ? 'border-primary bg-primary/10'
                      : 'border-transparent hover:bg-muted'
                  }`}
                >
                  {opt.icon}
                </button>
              ))}
            </div>
          </div>

          <div className="space-y-1">
            <label className="text-sm font-medium">
              {t('projects.description', 'Description')}
            </label>
            <Textarea
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              rows={2}
            />
          </div>

          <div className="space-y-1">
            <label className="text-sm font-medium">{t('projects.tags', 'Tags')}</label>
            <Input value={tags} onChange={(e) => setTags(e.target.value)} />
            <p className="text-2xs text-muted-foreground">
              {t('projects.tagsHint', 'Comma-separated')}
            </p>
          </div>

          <div className="space-y-1">
            <label className="text-sm font-medium">{t('projects.paths', 'Paths')}</label>
            <Textarea value={paths} onChange={(e) => setPaths(e.target.value)} rows={2} />
            <p className="text-2xs text-muted-foreground">
              {t('projects.pathsHint', 'One per line')}
            </p>
          </div>

          <div className="flex items-center justify-between">
            <label className="text-sm font-medium">
              {t('projects.memoryVisible', 'Memory Visible')}
            </label>
            <Switch checked={memoryVisible} onCheckedChange={setMemoryVisible} />
          </div>

          {/* RFC-025: Mount references */}
          {availableMounts.length > 0 && (
            <div className="space-y-1">
              <label className="text-sm font-medium">{t('projects.mounts', 'Mounts')}</label>
              <div className="flex flex-wrap gap-1">
                {availableMounts.map((m) => (
                  <button
                    key={m.id}
                    type="button"
                    onClick={() => {
                      setMountIds((prev) =>
                        prev.includes(m.id) ? prev.filter((id) => id !== m.id) : [...prev, m.id],
                      )
                    }}
                    className={`rounded px-2 py-1 text-xs border transition-colors ${
                      mountIds.includes(m.id)
                        ? 'border-primary bg-primary/10 text-primary'
                        : 'border-transparent hover:bg-muted'
                    }`}
                  >
                    <span className="inline-flex items-center gap-1">
                      <FolderOpen className="h-3 w-3" /> {m.name}
                    </span>
                  </button>
                ))}
              </div>
            </div>
          )}

          {/* RFC-025: Custom instructions */}
          <div className="space-y-1">
            <label className="text-sm font-medium">
              {t('projects.instructions', 'Instructions')}
            </label>
            <Textarea
              value={instructions}
              onChange={(e) => setInstructions(e.target.value)}
              rows={3}
              placeholder={t(
                'projects.instructionsPlaceholder',
                '이 Project에서 항상 지켜야 할 규칙. 시스템 프롬프트에 주입됩니다.',
              )}
            />
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={() => onOpenChange(false)}>
            {t('common.cancel', 'Cancel')}
          </Button>
          <Button onClick={handleSubmit} disabled={!name.trim() || update.isPending}>
            {update.isPending ? '...' : t('projects.save', 'Save')}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}