oxios 1.5.2

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { ArrowLeft, Edit, Trash2 } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { DeleteProjectDialog } from '@/components/project/delete-project-dialog'
import { EditProjectDialog } from '@/components/project/edit-project-dialog'
import { ErrorState } from '@/components/shared/error-state'
import { LoadingCards } from '@/components/shared/loading'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useProject, useProjectMemories } from '@/hooks/use-projects'
import { formatRelativeTime } from '@/lib/utils'
import type { Project } from '@/types'

export const Route = createFileRoute('/projects/$projectId')({
  component: ProjectDetailPage,
})

const SOURCE_COLORS: Record<string, string> = {
  manual: 'bg-success-subtle text-success',
  auto_detected: 'bg-warning-subtle text-warning',
}

function formatDate(iso: string) {
  return new Date(iso).toLocaleString()
}

function ProjectPathsCard({ project }: { project: Project }) {
  const { t } = useTranslation()

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">{t('projects.paths', 'Paths')}</CardTitle>
      </CardHeader>
      <CardContent>
        {project.paths && project.paths.length > 0 ? (
          <div className="space-y-2">
            {project.paths.map((path, i) => (
              <div key={i} className="flex items-center gap-2 text-sm">
                <span>📁</span>
                <code className="text-xs bg-muted px-2 py-1 rounded font-mono truncate">
                  {path}
                </code>
              </div>
            ))}
          </div>
        ) : (
          <p className="text-sm text-muted-foreground">
            {t('projects.noPaths', 'No paths — this is a non-code project')}
          </p>
        )}
      </CardContent>
    </Card>
  )
}

function ProjectDetailsCard({ project }: { project: Project }) {
  const { t } = useTranslation()
  const sourceColor = SOURCE_COLORS[project.source ?? 'manual'] ?? SOURCE_COLORS.manual

  const details = [
    { label: t('projects.source', 'Source'), value: project.source ?? 'manual' },
    {
      label: t('projects.memoryVisible', 'Memory Visible'),
      value: project.memory_visible ? 'Yes' : 'No',
    },
    { label: t('projects.createdAt', 'Created'), value: formatDate(project.created_at) },
    {
      label: t('projects.updatedAt', 'Updated'),
      value: formatDate(project.updated_at ?? project.created_at),
    },
    {
      label: t('projects.lastActive', 'Last Active'),
      value: formatRelativeTime(
        project.last_active_at ?? project.updated_at ?? project.created_at,
        t,
      ),
    },
  ]

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">{t('projects.details', 'Details')}</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="space-y-3">
          {/* Description */}
          {project.description && (
            <div>
              <p className="text-xs text-muted-foreground mb-1">
                {t('projects.description', 'Description')}
              </p>
              <p className="text-sm">{project.description}</p>
            </div>
          )}

          {/* Tags */}
          {project.tags && project.tags.length > 0 && (
            <div>
              <p className="text-xs text-muted-foreground mb-1">{t('projects.tags', 'Tags')}</p>
              <div className="flex flex-wrap gap-1">
                {project.tags.map((tag) => (
                  <Badge key={tag} variant="secondary" className="text-xs">
                    {tag}
                  </Badge>
                ))}
              </div>
            </div>
          )}

          {/* Other details */}
          <div className="grid gap-2 text-sm">
            {details.map((d) => (
              <div key={d.label} className="flex items-center justify-between">
                <span className="text-muted-foreground text-xs">{d.label}</span>
                <span
                  className={
                    d.label === 'Source'
                      ? `text-2xs px-1.5 py-0.5 rounded ${sourceColor}`
                      : 'text-xs'
                  }
                >
                  {d.value}
                </span>
              </div>
            ))}
          </div>
        </div>
      </CardContent>
    </Card>
  )
}

function ProjectMemoriesCard({ project }: { project: Project }) {
  const { t } = useTranslation()
  const { data, isLoading } = useProjectMemories(project.id)

  const memories = Array.isArray(data?.items) ? data.items : []

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base flex items-center gap-2">
          {t('projects.memories', 'Memories')}
          {memories.length > 0 && (
            <Badge variant="secondary" className="text-xs">
              {memories.length}
            </Badge>
          )}
        </CardTitle>
      </CardHeader>
      <CardContent>
        {isLoading ? (
          <div className="space-y-2">
            {[1, 2, 3].map((i) => (
              <div key={i} className="h-8 bg-muted rounded animate-pulse" />
            ))}
          </div>
        ) : memories.length === 0 ? (
          <p className="text-sm text-muted-foreground">
            {t('projects.noMemories', 'No memories linked to this project')}
          </p>
        ) : (
          <div className="space-y-2">
            {memories.map((mem: any) => (
              <div key={mem.id} className="flex items-center gap-2 p-2 rounded bg-muted/50">
                <Badge variant="outline" className="text-2xs shrink-0">
                  {mem.memory_type ?? mem.tier ?? 'memory'}
                </Badge>
                <p className="text-xs truncate flex-1">{mem.content?.slice(0, 80)}...</p>
              </div>
            ))}
          </div>
        )}
      </CardContent>
    </Card>
  )
}

function ProjectActivityCard() {
  const { t } = useTranslation()

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">{t('projects.activity', 'Activity')}</CardTitle>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-muted-foreground">
          {t('projects.activityDesc', 'Session history and memory changes — coming in Phase 3')}
        </p>
      </CardContent>
    </Card>
  )
}

function ProjectDetailPage() {
  const { t } = useTranslation()
  const navigate = useNavigate()
  const { projectId } = Route.useParams()

  const { data: project, isLoading, isError, refetch } = useProject(projectId)

  const [showEdit, setShowEdit] = useState(false)
  const [showDelete, setShowDelete] = useState(false)

  if (isLoading) return <LoadingCards count={4} />
  if (isError) return <ErrorState onRetry={() => refetch()} />
  if (!project)
    return <p className="text-muted-foreground">{t('projects.notFound', 'Project not found')}</p>

  return (
    <div className="space-y-4">
      {/* Back + Header */}
      <div className="flex items-center gap-4">
        <Button
          variant="ghost"
          size="icon"
          onClick={() => navigate({ to: '/projects' })}
          aria-label={t('common.back', 'Back')}
        >
          <ArrowLeft className="h-4 w-4" />
        </Button>
        <div className="flex items-center gap-2 flex-1 min-w-0">
          <span className="text-2xl">{project.emoji ?? '📦'}</span>
          <h1 className="text-2xl font-bold truncate">{project.name}</h1>
        </div>
        <div className="flex items-center gap-1 shrink-0">
          <Button variant="outline" size="sm" onClick={() => setShowEdit(true)}>
            <Edit className="h-3 w-3 mr-1" />
            {t('common.edit', 'Edit')}
          </Button>
          <Button variant="outline" size="sm" onClick={() => setShowDelete(true)}>
            <Trash2 className="h-3 w-3 mr-1" />
            {t('common.delete', 'Delete')}
          </Button>
        </div>
      </div>

      {/* Description */}
      {project.description && (
        <p className="text-muted-foreground text-sm mt-1">{project.description}</p>
      )}

      {/* Tabs */}
      <Tabs defaultValue="details" className="space-y-4">
        <TabsList>
          <TabsTrigger value="details">{t('projects.tabs.details', 'Details')}</TabsTrigger>
          <TabsTrigger value="paths">{t('projects.tabs.paths', 'Paths')}</TabsTrigger>
          <TabsTrigger value="memories">{t('projects.tabs.memories', 'Memories')}</TabsTrigger>
          <TabsTrigger value="activity">{t('projects.tabs.activity', 'Activity')}</TabsTrigger>
        </TabsList>

        <TabsContent value="details">
          <ProjectDetailsCard project={project} />
        </TabsContent>

        <TabsContent value="paths">
          <ProjectPathsCard project={project} />
        </TabsContent>

        <TabsContent value="memories">
          <ProjectMemoriesCard project={project} />
        </TabsContent>

        <TabsContent value="activity">
          <ProjectActivityCard />
        </TabsContent>
      </Tabs>

      {/* Dialogs */}
      <EditProjectDialog
        project={project}
        open={showEdit}
        onOpenChange={setShowEdit}
        onSuccess={() => {
          setShowEdit(false)
          refetch()
        }}
      />
      <DeleteProjectDialog project={project} open={showDelete} onOpenChange={setShowDelete} />
    </div>
  )
}