import { useState, useEffect, useCallback } from 'react'
import { useParams } from 'react-router-dom'
import { AppShell } from '../components/layout/AppShell'
import { Button } from '../components/ui/Button'
import { Input } from '../components/ui/Input'
import { Modal } from '../components/ui/Modal'
import { Badge } from '../components/ui/Badge'
import { TermLabel } from '../components/ui/Tooltip'
import { ImageGallery } from '../components/shared/ImageGallery'
import { useI18n } from '../i18n/useI18n'
import { api } from '../api/client'

interface Entity {
  id: string
  entity_type: string
  name: string
  description: string | null
  stable_traits: string
  t2i_prompt: string | null
  status: string
  episode_count: number
  created_at: string
}

interface EntityDetail extends Entity {
  aliases: string[]
  relations: Array<{
    id: string
    relation_family: string
    relation_type: string
    directionality: string
    temporal_scope: string | null
    continuity_priority: string | null
    continuity_reason: string | null
    participants: Array<{
      entity_name: string
      entity_id: string
      role: string
      order: number
    }>
  }>
  episodes: Array<{
    episode_id: string
    episode_number: number
    title: string
  }>
}

interface ImageAsset {
  id: string
  asset_type: string
  entity_id: string | null
  still_id: string | null
  episode_id: string | null
  file_path: string
  prompt_used: string | null
  status: string
  is_primary: boolean
  created_at: string
}

type FilterType = 'all' | 'character' | 'location' | 'prop' | 'outlook' | 'char_outlook'

function entityTypeLabel(type: string, t: (k: string) => string): string {
  const map: Record<string, string> = {
    character: t('entity.type.character'),
    location: t('entity.type.location'),
    prop: t('entity.type.prop'),
    outlook: t('entity.type.outlook'),
  }
  return map[type] ?? type
}

function entityTypeVariant(type: string): 'default' | 'green' | 'orange' {
  switch (type) {
    case 'character': return 'default'
    case 'location': return 'green'
    case 'prop': return 'orange'
    case 'outlook': return 'default'
    default: return 'default'
  }
}

interface CharOutlookCombo {
  character_name: string
  character_id: string
  outlook_name: string
  outlook_id: string
}

function relationFamilyLabel(family: string): string {
  const map: Record<string, string> = {
    kinship: 'kinship',
    conflict: 'conflict',
    possession: 'possession',
    affiliation: 'affiliation',
    location: 'location',
    emotional: 'emotional',
  }
  return map[family] ?? family
}

function relationFamilyColor(family: string): string {
  const map: Record<string, string> = {
    kinship: 'var(--green)',
    conflict: 'var(--red)',
    possession: 'var(--orange)',
    affiliation: 'var(--accent)',
    location: 'var(--yellow)',
    emotional: '#c084fc',
  }
  return map[family] ?? 'var(--text-muted)'
}

function parseStableTraits(traitsStr: string): Record<string, unknown> {
  try {
    return JSON.parse(traitsStr)
  } catch {
    return {}
  }
}

export function Entities() {
  const { id } = useParams<{ id: string }>()
  const { t } = useI18n()

  const [entities, setEntities] = useState<Entity[]>([])
  const [loading, setLoading] = useState(true)
  const [filter, setFilter] = useState<FilterType>('all')
  const [search, setSearch] = useState('')

  const [selectedEntity, setSelectedEntity] = useState<EntityDetail | null>(null)
  const [generatingAll, setGeneratingAll] = useState(false)
  const [detailLoading, setDetailLoading] = useState(false)
  const [detailOpen, setDetailOpen] = useState(false)

  const [editDesc, setEditDesc] = useState('')
  const [editName, setEditName] = useState('')
  const [editTraits, setEditTraits] = useState('')
  const [saving, setSaving] = useState(false)

  // Generation status for resume/full buttons
  const [refStatus, setRefStatus] = useState<{ ref_total: number; ref_done: number } | null>(null)

  // Entity images
  const [entityImages, setEntityImages] = useState<ImageAsset[]>([])
  const [generatingEntityImage, setGeneratingEntityImage] = useState(false)

  // Character+Outlook combos
  const [charOutlookCombos, setCharOutlookCombos] = useState<CharOutlookCombo[]>([])

  // Char+Outlook detail modal state
  const [charOutlookDetailOpen, setCharOutlookDetailOpen] = useState(false)
  const [charOutlookDetailCombo, setCharOutlookDetailCombo] = useState<CharOutlookCombo | null>(null)
  const [charOutlookImages, setCharOutlookImages] = useState<ImageAsset[]>([])
  const [charOutlookFaceImage, setCharOutlookFaceImage] = useState<ImageAsset | null>(null)
  const [charOutlookDetailLoading, setCharOutlookDetailLoading] = useState(false)

  const fetchEntities = useCallback(async () => {
    if (!id) return
    setLoading(true)
    try {
      const params = filter !== 'all' ? `?type=${filter}` : ''
      const data = await api<Entity[]>(`/api/v1/projects/${id}/entities${params}`)
      setEntities(data)
    } catch {
      setEntities([])
    } finally {
      setLoading(false)
    }
  }, [id, filter])

  const fetchRefStatus = useCallback(async () => {
    if (!id) return
    try {
      const episodes = await api<any[]>(`/api/v1/projects/${id}/episodes/`)
      let totalRef = 0, doneRef = 0
      for (const ep of episodes) {
        const s = await api<{ ref_total: number; ref_done: number }>(
          `/api/v1/projects/${id}/episodes/${ep.id}/generation-status`
        )
        totalRef += s.ref_total
        doneRef += s.ref_done
      }
      setRefStatus({ ref_total: totalRef, ref_done: doneRef })
    } catch {
      // ignore
    }
  }, [id])

  const fetchCharOutlookCombos = useCallback(async () => {
    if (!id) return
    try {
      const data = await api<CharOutlookCombo[]>(`/api/v1/projects/${id}/character-outlooks`)
      setCharOutlookCombos(data)
    } catch {
      setCharOutlookCombos([])
    }
  }, [id])

  useEffect(() => {
    fetchEntities()
    fetchRefStatus()
    fetchCharOutlookCombos()
  }, [fetchEntities, fetchRefStatus, fetchCharOutlookCombos])

  const fetchEntityImages = useCallback(async (entityId: string, entityType?: string) => {
    if (!id) return
    try {
      if (entityType === 'outlook') {
        // Bug 3 fix: outlook entities store composite images on character_id
        // with prompt_used containing outlook_id:{outlook_id}
        const data = await api<ImageAsset[]>(`/api/v1/projects/${id}/images?outlook_id=${entityId}`)
        setEntityImages(data)
      } else {
        const data = await api<ImageAsset[]>(`/api/v1/projects/${id}/images?entity_id=${entityId}`)
        setEntityImages(data)
      }
    } catch {
      setEntityImages([])
    }
  }, [id])

  const openDetail = async (entityId: string) => {
    setDetailOpen(true)
    setDetailLoading(true)
    try {
      const data = await api<EntityDetail>(`/api/v1/projects/${id}/entities/${entityId}`)
      setSelectedEntity(data)
      setEditName(data.name)
      setEditDesc(data.description ?? '')
      setEditTraits(data.stable_traits || '{}')
      fetchEntityImages(entityId, data.entity_type)
    } catch {
      setSelectedEntity(null)
    } finally {
      setDetailLoading(false)
    }
  }

  // Issue 5: Open char+outlook detail modal
  const openCharOutlookDetail = async (combo: CharOutlookCombo) => {
    setCharOutlookDetailOpen(true)
    setCharOutlookDetailLoading(true)
    setCharOutlookDetailCombo(combo)
    try {
      // Fetch composite images (stored on character_id with outlook_id in prompt_used)
      const compositeImages = await api<ImageAsset[]>(
        `/api/v1/projects/${id}/images?entity_id=${combo.character_id}&outlook_id=${combo.outlook_id}&type=reference`
      )
      setCharOutlookImages(compositeImages)

      // Fetch face image (primary reference for character, without outlook_id in prompt_used)
      const charImages = await api<ImageAsset[]>(
        `/api/v1/projects/${id}/images?entity_id=${combo.character_id}&type=reference`
      )
      const faceImg = charImages.find(img =>
        !(img.prompt_used || '').includes('outlook_id:') && img.is_primary
      ) || charImages.find(img =>
        !(img.prompt_used || '').includes('outlook_id:')
      ) || null
      setCharOutlookFaceImage(faceImg)
    } catch {
      setCharOutlookImages([])
      setCharOutlookFaceImage(null)
    } finally {
      setCharOutlookDetailLoading(false)
    }
  }

  const handleSave = async () => {
    if (!selectedEntity || !id) return
    setSaving(true)
    try {
      const body: Record<string, string> = { name: editName, description: editDesc }
      // Only send stable_traits if it was edited
      if (editTraits !== selectedEntity.stable_traits) {
        body.stable_traits = editTraits
      }
      await api(`/api/v1/projects/${id}/entities/${selectedEntity.id}`, {
        method: 'PATCH',
        body: JSON.stringify(body),
      })
      setDetailOpen(false)
      setSelectedEntity(null)
      await fetchEntities()
    } catch {
      // ignore
    } finally {
      setSaving(false)
    }
  }

  const handleGenerateEntityImage = async () => {
    if (!selectedEntity || !id) return
    setGeneratingEntityImage(true)
    try {
      await api(`/api/v1/projects/${id}/entities/${selectedEntity.id}/generate-image`, {
        method: 'POST',
        body: JSON.stringify({}),
      })
      await fetchEntityImages(selectedEntity.id)
    } catch {
      // ignore
    } finally {
      setGeneratingEntityImage(false)
    }
  }

  const handleSetPrimaryImage = async (imageId: string) => {
    if (!selectedEntity || !id) return
    try {
      await api(`/api/v1/projects/${id}/images/${imageId}/set-primary`, {
        method: 'POST',
      })
      await fetchEntityImages(selectedEntity.id)
    } catch {
      // ignore
    }
  }

  const handleUploadEntityImage = async (file: File) => {
    if (!selectedEntity || !id) return
    const formData = new FormData()
    formData.append('file', file)
    formData.append('entity_id', selectedEntity.id)
    try {
      await fetch(`/api/v1/projects/${id}/images/upload`, {
        method: 'POST',
        credentials: 'include',
        body: formData,
      })
      await fetchEntityImages(selectedEntity.id)
    } catch {
      // ignore
    }
  }

  const filters: { key: FilterType; label: string; color?: string }[] = [
    { key: 'all', label: t('entity.filter.all') },
    { key: 'character', label: t('entity.type.character') },
    { key: 'location', label: t('entity.type.location') },
    { key: 'prop', label: t('entity.type.prop') },
    { key: 'outlook', label: t('entity.type.outlook'), color: 'var(--purple, #8b5cf6)' },
    { key: 'char_outlook', label: t('entity.char_outlook_tab') || '인물+아웃룩', color: 'var(--pink, #ec4899)' },
  ]

  // Group relations by family
  const groupedRelations = selectedEntity
    ? selectedEntity.relations.reduce<Record<string, typeof selectedEntity.relations>>((acc, rel) => {
        const family = rel.relation_family
        if (!acc[family]) acc[family] = []
        acc[family].push(rel)
        return acc
      }, {})
    : {}

  // Parse stable traits for display
  const stableTraits = selectedEntity ? parseStableTraits(selectedEntity.stable_traits) : {}
  const visualTraits = (stableTraits.visual_anchor_traits ?? []) as string[]

  return (
    <AppShell title={t('nav.project.entities')} projectId={id}>
      <div className="page">
        <div className="card">
          <div className="card-header">
            <h2>
              <TermLabel label={t('nav.project.entities')} term={t('term.entity')} />
            </h2>
            {/* 미완료: 이어서 + 전체 / 완료 또는 0: 전체만 */}
            {refStatus && refStatus.ref_done > 0 && refStatus.ref_done < refStatus.ref_total && (
              <Button
                size="sm"
                disabled={generatingAll}
                onClick={async () => {
                  if (!id) return
                  setGeneratingAll(true)
                  try {
                    const episodes = await api<any[]>(`/api/v1/projects/${id}/episodes/`)
                    for (const ep of episodes) {
                      await api(`/api/v1/projects/${id}/episodes/${ep.id}/generate-reference-images?mode=resume`, { method: 'POST' })
                    }
                  } catch { /* ignore */ } finally { setGeneratingAll(false) }
                }}
              >
                {generatingAll ? t('common.loading') : `${t('image.resume_generate')} (${refStatus.ref_done}/${refStatus.ref_total})`}
              </Button>
            )}
            <Button
              size="sm"
              variant={refStatus && refStatus.ref_done > 0 && refStatus.ref_done < refStatus.ref_total ? 'secondary' : 'primary'}
              disabled={generatingAll}
              onClick={async () => {
                if (!id) return
                setGeneratingAll(true)
                try {
                  const episodes = await api<any[]>(`/api/v1/projects/${id}/episodes/`)
                  for (const ep of episodes) {
                    await api(`/api/v1/projects/${id}/episodes/${ep.id}/generate-reference-images?mode=full`, { method: 'POST' })
                  }
                } catch { /* ignore */ } finally { setGeneratingAll(false) }
              }}
            >
              {generatingAll ? t('common.loading') : t('entity.generate_all_references')}
            </Button>
          </div>

          {/* Filter tabs */}
          <div className="filter-bar">
            {filters.map((f) => (
              <button
                key={f.key}
                className={`filter-btn${filter === f.key ? ' active' : ''}`}
                style={f.color ? {
                  borderColor: filter === f.key ? f.color : undefined,
                  color: filter === f.key ? f.color : undefined,
                } : undefined}
                onClick={() => setFilter(f.key)}
              >
                {f.label}
              </button>
            ))}
          </div>

          {/* Entity search */}
          {filter !== 'char_outlook' && (
            <div style={{ marginBottom: '12px' }}>
              <input
                type="text"
                placeholder="요소 검색 (이름/설명)..."
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                style={{
                  width: '100%', maxWidth: '300px', padding: '6px 12px',
                  borderRadius: '6px', border: '1px solid var(--border)',
                  background: 'var(--bg-input)', color: 'var(--text-primary)',
                  fontSize: '13px',
                }}
              />
            </div>
          )}

          {loading ? (
            <div className="empty-state">{t('common.loading')}</div>
          ) : filter === 'char_outlook' ? (
            /* 인물+아웃룩 탭 */
            charOutlookCombos.length === 0 ? (
              <div className="empty-state">{t('entity.outlook_no_items') || '인물+아웃룩 조합이 없습니다'}</div>
            ) : (
              <div style={{
                display: 'grid',
                gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
                gap: '14px',
              }}>
                {charOutlookCombos.map((combo, idx) => (
                  <div
                    key={`${combo.character_id}-${combo.outlook_id}-${idx}`}
                    style={{
                      background: 'var(--bg-raised)',
                      border: '1px solid var(--border)',
                      borderLeft: '3px solid var(--pink, #ec4899)',
                      borderRadius: 'var(--radius)',
                      padding: '16px 18px',
                      cursor: 'pointer',
                    }}
                    onClick={() => openCharOutlookDetail(combo)}
                  >
                    <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '6px' }}>
                      <span style={{ fontWeight: 600, fontSize: '14px' }}>
                        {combo.character_name} + {combo.outlook_name}
                      </span>
                      <Badge label="인물+아웃룩" variant="default" />
                    </div>
                    <div style={{ fontSize: '12px', color: 'var(--text-dim)' }}>
                      [[{combo.character_name}]+[{combo.outlook_name}]]
                    </div>
                  </div>
                ))}
              </div>
            )
          ) : entities.length === 0 ? (
            <div className="empty-state">{t('entity.no_entities')}</div>
          ) : (
            <div style={{
              display: 'grid',
              gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))',
              gap: '14px',
            }}>
              {entities
                .filter(e => filter === 'all' ? e.entity_type !== 'outlook' : e.entity_type === filter)
                .filter(e => !search || e.name.includes(search) || (e.description || '').includes(search))
                .map((entity) => (
                <div
                  key={entity.id}
                  style={{
                    background: 'var(--bg-raised)',
                    border: '1px solid var(--border)',
                    borderRadius: 'var(--radius)',
                    padding: '16px 18px',
                    cursor: 'pointer',
                    transition: 'border-color 0.15s',
                  }}
                  onClick={() => openDetail(entity.id)}
                  onMouseEnter={(e) => {
                    (e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)'
                  }}
                  onMouseLeave={(e) => {
                    (e.currentTarget as HTMLElement).style.borderColor = 'var(--border)'
                  }}
                >
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '8px' }}>
                    <span style={{ fontWeight: 600, fontSize: '14px', fontFamily: 'var(--font-serif)' }}>
                      {entity.name}
                    </span>
                    <Badge
                      label={entityTypeLabel(entity.entity_type, t)}
                      variant={entityTypeVariant(entity.entity_type)}
                    />
                  </div>
                  {entity.description && (
                    <p style={{
                      fontSize: '13px',
                      color: 'var(--text-muted)',
                      lineHeight: 1.5,
                      marginBottom: '10px',
                      display: '-webkit-box',
                      WebkitLineClamp: 3,
                      WebkitBoxOrient: 'vertical',
                      overflow: 'hidden',
                    }}>
                      {entity.description}
                    </p>
                  )}
                  <div style={{ fontSize: '12px', color: 'var(--text-dim)' }}>
                    {t('entity.episodes')}: {entity.episode_count}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Entity detail modal */}
      <Modal
        open={detailOpen}
        title={selectedEntity?.name ?? t('entity.detail')}
        onClose={() => { setDetailOpen(false); setSelectedEntity(null); setEntityImages([]) }}
      >
        {detailLoading ? (
          <div className="empty-state">{t('common.loading')}</div>
        ) : selectedEntity ? (
          <div className="form-stack">
            {/* Type badge */}
            <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
              <Badge
                label={entityTypeLabel(selectedEntity.entity_type, t)}
                variant={entityTypeVariant(selectedEntity.entity_type)}
              />
              <span style={{ fontSize: '12px', color: 'var(--text-dim)' }}>
                {t('entity.episodes')}: {selectedEntity.episode_count}
              </span>
            </div>

            {/* Image gallery -- grouped by type for characters (Issue 4) */}
            {id && selectedEntity.entity_type === 'character' && entityImages.length > 0 ? (
              <div>
                <div style={{
                  display: 'flex',
                  justifyContent: 'space-between',
                  alignItems: 'center',
                  marginBottom: '10px',
                }}>
                  <span style={{
                    fontSize: '12px',
                    fontWeight: 600,
                    color: 'var(--text-muted)',
                    textTransform: 'uppercase',
                    letterSpacing: '0.04em',
                  }}>
                    {t('image.type.reference')} ({entityImages.length})
                  </span>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <Button
                      size="sm"
                      variant="secondary"
                      disabled={generatingEntityImage}
                      onClick={handleGenerateEntityImage}
                    >
                      {generatingEntityImage ? t('still.generating') : t('image.generate_single')}
                    </Button>
                  </div>
                </div>
                {/* Face images (no outlook_id in prompt_used) */}
                {(() => {
                  const faceImages = entityImages.filter(img => !(img.prompt_used || '').includes('outlook_id:'))
                  const outfitImages = entityImages.filter(img => (img.prompt_used || '').includes('outlook_id:'))

                  // Parse outlook names from prompt_used or match from charOutlookCombos
                  const outfitGroups: Record<string, { name: string; images: ImageAsset[] }> = {}
                  for (const img of outfitImages) {
                    const match = (img.prompt_used || '').match(/outlook_id:([a-f0-9-]+)/)
                    const outlookId = match ? match[1] : 'unknown'
                    if (!outfitGroups[outlookId]) {
                      const combo = charOutlookCombos.find(c => c.outlook_id === outlookId)
                      outfitGroups[outlookId] = {
                        name: combo?.outlook_name || outlookId.slice(0, 8),
                        images: [],
                      }
                    }
                    outfitGroups[outlookId].images.push(img)
                  }

                  return (
                    <>
                      {faceImages.length > 0 && (
                        <div style={{ marginBottom: '12px' }}>
                          <div style={{
                            fontSize: '11px',
                            fontWeight: 700,
                            color: 'var(--accent)',
                            textTransform: 'uppercase',
                            letterSpacing: '0.06em',
                            marginBottom: '6px',
                          }}>
                            {'얼굴'}
                          </div>
                          <div style={{
                            display: 'grid',
                            gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))',
                            gap: '8px',
                          }}>
                            {faceImages.map((img) => (
                              <div
                                key={img.id}
                                style={{
                                  position: 'relative',
                                  borderRadius: 'var(--radius-sm)',
                                  overflow: 'hidden',
                                  border: img.is_primary
                                    ? '2px solid var(--green)'
                                    : '1px solid var(--border)',
                                  cursor: 'pointer',
                                }}
                                onClick={() => handleSetPrimaryImage(img.id)}
                              >
                                <img
                                  src={`/api/v1/projects/${id}/images/${img.id}/file?thumb=1`}
                                  alt={img.id}
                                  style={{ width: '100%', height: '80px', objectFit: 'cover', display: 'block' }}
                                  onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
                                />
                                {img.is_primary && (
                                  <div style={{
                                    position: 'absolute', top: '4px', left: '4px',
                                    background: 'var(--green)', color: '#fff',
                                    fontSize: '9px', fontWeight: 700,
                                    padding: '1px 5px', borderRadius: '999px',
                                  }}>
                                    *
                                  </div>
                                )}
                              </div>
                            ))}
                          </div>
                        </div>
                      )}
                      {Object.entries(outfitGroups).map(([outlookId, group]) => (
                        <div key={outlookId} style={{ marginBottom: '12px' }}>
                          <div style={{
                            fontSize: '11px',
                            fontWeight: 700,
                            color: 'var(--purple, #8b5cf6)',
                            textTransform: 'uppercase',
                            letterSpacing: '0.06em',
                            marginBottom: '6px',
                          }}>
                            {group.name}
                            {group.images.length > 0 && group.images[0] ===
                              group.images.reduce((latest, img) =>
                                img.created_at > latest.created_at ? img : latest, group.images[0])
                              && ' (*)'}
                          </div>
                          <div style={{
                            display: 'grid',
                            gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))',
                            gap: '8px',
                          }}>
                            {group.images.map((img) => (
                              <div
                                key={img.id}
                                style={{
                                  position: 'relative',
                                  borderRadius: 'var(--radius-sm)',
                                  overflow: 'hidden',
                                  border: img.is_primary
                                    ? '2px solid var(--green)'
                                    : '1px solid var(--border)',
                                  cursor: 'pointer',
                                }}
                                onClick={() => handleSetPrimaryImage(img.id)}
                              >
                                <img
                                  src={`/api/v1/projects/${id}/images/${img.id}/file?thumb=1`}
                                  alt={img.id}
                                  style={{ width: '100%', height: '80px', objectFit: 'cover', display: 'block' }}
                                  onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
                                />
                                {img.is_primary && (
                                  <div style={{
                                    position: 'absolute', top: '4px', left: '4px',
                                    background: 'var(--green)', color: '#fff',
                                    fontSize: '9px', fontWeight: 700,
                                    padding: '1px 5px', borderRadius: '999px',
                                  }}>
                                    *
                                  </div>
                                )}
                              </div>
                            ))}
                          </div>
                        </div>
                      ))}
                      {faceImages.length === 0 && Object.keys(outfitGroups).length === 0 && (
                        <div style={{
                          padding: '16px', textAlign: 'center', fontSize: '13px',
                          color: 'var(--text-dim)', background: 'var(--bg-input)',
                          borderRadius: 'var(--radius-sm)',
                        }}>
                          {t('common.no_data')}
                        </div>
                      )}
                    </>
                  )
                })()}
              </div>
            ) : id ? (
              <ImageGallery
                projectId={id}
                images={entityImages}
                onSetPrimary={handleSetPrimaryImage}
                onGenerate={handleGenerateEntityImage}
                onUpload={handleUploadEntityImage}
                title={t('image.type.reference')}
                generating={generatingEntityImage}
              />
            ) : null}

            {/* Aliases */}
            {selectedEntity.aliases.length > 0 && (
              <div>
                <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' }}>
                  {t('entity.aliases')}
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                  {selectedEntity.aliases.map((alias, i) => (
                    <span key={i} style={{
                      padding: '2px 10px',
                      background: 'var(--bg-input)',
                      borderRadius: '999px',
                      fontSize: '12px',
                      color: 'var(--text-muted)',
                    }}>
                      {alias}
                    </span>
                  ))}
                </div>
              </div>
            )}

            {/* Visual traits from stable_traits */}
            {visualTraits.length > 0 && (
              <div>
                <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' }}>
                  <TermLabel label={t('entity.stable_traits')} term={t('term.canon')} />
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                  {visualTraits.map((trait, i) => (
                    <span key={i} style={{
                      padding: '3px 10px',
                      background: 'var(--accent-glow)',
                      borderRadius: '999px',
                      fontSize: '12px',
                      color: 'var(--accent)',
                    }}>
                      {trait}
                    </span>
                  ))}
                </div>
              </div>
            )}

            {/* T2I Prompt */}
            {selectedEntity.t2i_prompt && (
              <div>
                <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' }}>
                  T2I Prompt
                </div>
                <div style={{
                  padding: '10px 14px',
                  background: 'var(--bg-input)',
                  borderRadius: 'var(--radius-sm)',
                  fontSize: '12px',
                  color: 'var(--text)',
                  fontFamily: 'var(--font-mono)',
                  lineHeight: 1.6,
                  whiteSpace: 'pre-wrap',
                }}>
                  {selectedEntity.t2i_prompt}
                </div>
              </div>
            )}

            {/* Relations -- grouped by family */}
            {selectedEntity.relations.length > 0 && (
              <div>
                <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' }}>
                  <TermLabel label={t('entity.relations')} term={t('term.relation')} />
                </div>
                <div style={{ display: 'grid', gap: '8px' }}>
                  {Object.entries(groupedRelations).map(([family, rels]) => (
                    <div key={family}>
                      <div style={{
                        fontSize: '11px',
                        fontWeight: 700,
                        color: relationFamilyColor(family),
                        textTransform: 'uppercase',
                        letterSpacing: '0.06em',
                        marginBottom: '4px',
                      }}>
                        {relationFamilyLabel(family)}
                      </div>
                      <div style={{ display: 'grid', gap: '4px' }}>
                        {rels.map((rel) => (
                          <div key={rel.id} style={{
                            background: 'var(--bg-raised)',
                            borderRadius: 'var(--radius-sm)',
                            padding: '8px 12px',
                            fontSize: '12px',
                            borderLeft: `3px solid ${relationFamilyColor(family)}`,
                          }}>
                            <span style={{ fontWeight: 600, color: 'var(--text)' }}>{rel.relation_type}</span>
                            {rel.directionality === 'directed' && (
                              <span style={{ color: 'var(--text-dim)', margin: '0 4px' }}>&rarr;</span>
                            )}
                            {rel.directionality !== 'directed' && (
                              <span style={{ color: 'var(--text-dim)', margin: '0 4px' }}>&mdash;</span>
                            )}
                            <span style={{ color: 'var(--text-muted)' }}>
                              {rel.participants
                                .filter(p => p.entity_id !== selectedEntity.id)
                                .map((p) => (
                                  <span
                                    key={p.entity_id}
                                    style={{ cursor: 'pointer', color: 'var(--accent)', textDecoration: 'underline' }}
                                    onClick={(e) => {
                                      e.stopPropagation()
                                      openDetail(p.entity_id)
                                    }}
                                  >
                                    {p.entity_name}
                                  </span>
                                ))
                                .reduce<React.ReactNode[]>((acc, el, i) => {
                                  if (i > 0) acc.push(<span key={`sep-${i}`}>, </span>)
                                  acc.push(el)
                                  return acc
                                }, [])
                              }
                            </span>
                            {rel.continuity_reason && (
                              <div style={{ fontSize: '11px', color: 'var(--text-dim)', marginTop: '2px' }}>
                                {rel.continuity_reason}
                              </div>
                            )}
                          </div>
                        ))}
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {/* Relation graph -- simple list view */}
            {selectedEntity.relations.length > 0 && (
              <div>
                <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' }}>
                  {t('entity.relation_graph')}
                </div>
                <div style={{
                  background: 'var(--bg-raised)',
                  borderRadius: 'var(--radius-sm)',
                  padding: '12px 14px',
                }}>
                  <div style={{ textAlign: 'center', fontWeight: 600, fontSize: '14px', marginBottom: '8px', color: 'var(--accent)' }}>
                    {selectedEntity.name}
                  </div>
                  <div style={{ display: 'grid', gap: '4px' }}>
                    {selectedEntity.relations.map((rel) => {
                      const others = rel.participants.filter(p => p.entity_id !== selectedEntity.id)
                      return others.map((other) => (
                        <div key={`${rel.id}-${other.entity_id}`} style={{
                          display: 'flex',
                          alignItems: 'center',
                          gap: '8px',
                          fontSize: '12px',
                          padding: '4px 0',
                        }}>
                          <span style={{ color: relationFamilyColor(rel.relation_family), fontWeight: 600, minWidth: '60px' }}>
                            {rel.relation_type}
                          </span>
                          <span style={{ color: 'var(--text-dim)' }}>
                            {rel.directionality === 'directed' ? '\u2192' : '\u2194'}
                          </span>
                          <span
                            style={{ color: 'var(--accent)', cursor: 'pointer' }}
                            onClick={() => openDetail(other.entity_id)}
                          >
                            {other.entity_name}
                          </span>
                          <span style={{ fontSize: '10px', color: 'var(--text-dim)' }}>
                            ({other.role})
                          </span>
                        </div>
                      ))
                    })}
                  </div>
                </div>
              </div>
            )}

            {/* Episode appearances */}
            {selectedEntity.episodes && selectedEntity.episodes.length > 0 && (
              <div>
                <div style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px' }}>
                  {t('entity.episode_appearances')}
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                  {selectedEntity.episodes.map((ep) => (
                    <span key={ep.episode_id} style={{
                      padding: '3px 10px',
                      background: 'var(--bg-input)',
                      borderRadius: '999px',
                      fontSize: '12px',
                      color: 'var(--text-muted)',
                      fontFamily: 'var(--font-mono)',
                    }}>
                      EP{ep.episode_number} -- {ep.title}
                    </span>
                  ))}
                </div>
              </div>
            )}

            {/* Edit name */}
            <Input
              id="entity-name"
              label={t('project.name')}
              type="text"
              value={editName}
              onChange={(e) => setEditName(e.target.value)}
            />

            {/* Edit description */}
            <div className="field">
              <label htmlFor="entity-desc">{t('entity.description')}</label>
              <textarea
                id="entity-desc"
                rows={4}
                value={editDesc}
                onChange={(e) => setEditDesc(e.target.value)}
              />
            </div>

            {/* Edit stable traits */}
            <div className="field">
              <label htmlFor="entity-traits">{t('entity.stable_traits_edit')}</label>
              <textarea
                id="entity-traits"
                rows={3}
                value={editTraits}
                onChange={(e) => setEditTraits(e.target.value)}
                style={{ fontFamily: 'var(--font-mono)', fontSize: '12px' }}
              />
            </div>

            <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
              <Button
                type="button"
                variant="ghost"
                onClick={() => { setDetailOpen(false); setSelectedEntity(null); setEntityImages([]) }}
              >
                {t('btn.cancel')}
              </Button>
              <Button
                type="button"
                variant="primary"
                disabled={saving}
                onClick={handleSave}
              >
                {saving ? t('common.loading') : t('btn.save')}
              </Button>
            </div>
          </div>
        ) : (
          <div className="empty-state">{t('common.no_data')}</div>
        )}
      </Modal>

      {/* Issue 5: Char+Outlook detail modal */}
      <Modal
        open={charOutlookDetailOpen}
        title={charOutlookDetailCombo ? `${charOutlookDetailCombo.character_name} + ${charOutlookDetailCombo.outlook_name}` : ''}
        onClose={() => {
          setCharOutlookDetailOpen(false)
          setCharOutlookDetailCombo(null)
          setCharOutlookImages([])
          setCharOutlookFaceImage(null)
        }}
        size="large"
      >
        {charOutlookDetailLoading ? (
          <div className="empty-state">{t('common.loading')}</div>
        ) : charOutlookDetailCombo ? (
          <div className="form-stack">
            <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
              <Badge label="인물+아웃룩" variant="default" />
            </div>

            {/* Source info */}
            <div style={{
              padding: '12px 16px',
              background: 'var(--bg-input)',
              borderRadius: 'var(--radius-sm)',
              fontSize: '13px',
              color: 'var(--text-muted)',
              lineHeight: 1.6,
            }}>
              {'이 이미지는 '}
              <span style={{ color: 'var(--accent)', fontWeight: 600 }}>
                [{charOutlookDetailCombo.character_name} 얼굴 이미지]
              </span>
              {' + '}
              <span style={{ color: 'var(--purple, #8b5cf6)', fontWeight: 600 }}>
                [{charOutlookDetailCombo.outlook_name} 설명]
              </span>
              {'으로 생성되었습니다'}
            </div>

            {/* Source face image */}
            {charOutlookFaceImage && id && (
              <div>
                <div style={{
                  fontSize: '11px', fontWeight: 700, color: 'var(--accent)',
                  textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '6px',
                }}>
                  {'얼굴 원본'}
                </div>
                <div style={{
                  display: 'inline-block',
                  borderRadius: 'var(--radius-sm)',
                  overflow: 'hidden',
                  border: '1px solid var(--border)',
                }}>
                  <img
                    src={`/api/v1/projects/${id}/images/${charOutlookFaceImage.id}/file?thumb=1`}
                    alt="face"
                    style={{ width: '120px', height: '120px', objectFit: 'cover', display: 'block' }}
                    onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
                  />
                </div>
              </div>
            )}

            {/* Composite images */}
            <div>
              <div style={{
                fontSize: '11px', fontWeight: 700, color: 'var(--purple, #8b5cf6)',
                textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: '6px',
              }}>
                {'합성 이미지'} ({charOutlookImages.length})
              </div>
              {charOutlookImages.length === 0 ? (
                <div style={{
                  padding: '16px', textAlign: 'center', fontSize: '13px',
                  color: 'var(--text-dim)', background: 'var(--bg-input)',
                  borderRadius: 'var(--radius-sm)',
                }}>
                  {t('common.no_data')}
                </div>
              ) : (
                <div style={{
                  display: 'grid',
                  gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
                  gap: '10px',
                }}>
                  {charOutlookImages.map((img) => (
                    <div
                      key={img.id}
                      style={{
                        position: 'relative',
                        borderRadius: 'var(--radius-sm)',
                        overflow: 'hidden',
                        border: img.is_primary
                          ? '2px solid var(--green)'
                          : '1px solid var(--border)',
                      }}
                    >
                      {id && (
                        <img
                          src={`/api/v1/projects/${id}/images/${img.id}/file?thumb=1`}
                          alt={img.id}
                          style={{ width: '100%', height: '160px', objectFit: 'cover', display: 'block' }}
                          onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
                        />
                      )}
                      {img.is_primary && (
                        <div style={{
                          position: 'absolute', top: '4px', left: '4px',
                          background: 'var(--green)', color: '#fff',
                          fontSize: '9px', fontWeight: 700,
                          padding: '1px 5px', borderRadius: '999px',
                        }}>
                          {t('image.is_primary')}
                        </div>
                      )}
                    </div>
                  ))}
                </div>
              )}
            </div>

            {/* Prompt used */}
            {charOutlookImages.length > 0 && charOutlookImages[0].prompt_used && (
              <div>
                <div style={{
                  fontSize: '12px', fontWeight: 600, color: 'var(--text-muted)',
                  textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '6px',
                }}>
                  {'아웃룩 설명'}
                </div>
                <div style={{
                  padding: '10px 14px',
                  background: 'var(--bg-input)',
                  borderRadius: 'var(--radius-sm)',
                  fontSize: '12px',
                  color: 'var(--text)',
                  fontFamily: 'var(--font-mono)',
                  lineHeight: 1.6,
                  whiteSpace: 'pre-wrap',
                }}>
                  {/* Remove the [outlook_id:xxx] prefix from display */}
                  {(charOutlookImages[0].prompt_used || '').replace(/\[outlook_id:[a-f0-9-]+\]\s*/, '')}
                </div>
              </div>
            )}
          </div>
        ) : (
          <div className="empty-state">{t('common.no_data')}</div>
        )}
      </Modal>
    </AppShell>
  )
}
