import { useState, useEffect, useCallback, useRef } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { AppShell } from '../components/layout/AppShell'
import { Badge } from '../components/ui/Badge'
import { Button } from '../components/ui/Button'
import { PipelineProgressPanel } from '../components/shared/PipelineProgress'
import { SceneVariationCard } from '../components/shared/SceneVariationCard'
import { GenerationStatusPanel } from '../components/shared/GenerationStatusPanel'
import { LLMConfigPanel } from '../components/shared/LLMConfigPanel'
import type { AllProgress } from '../components/shared/PipelineProgress'
import { useI18n } from '../i18n/useI18n'
import { useToast } from '../hooks/useToast'
import { api } from '../api/client'

interface Episode {
  id: string
  episode_number: number
  title: string
  source_filename: string
  language: string
  page_count: number | null
  status: string
  summary: string | null
  created_at: string
}

interface ResolvedEntity {
  entity_id: string
  entity_name: string
  entity_type: string
  t2i_prompt: string
  has_reference_image: boolean
  reference_image_id: string | null
}

interface SceneStill {
  id: string
  episode_id: string
  still_index: number
  screenplay_scene_heading: string | null
  beat_title: string | null
  still_frame_prompt: string | null
  camera_json: string
  lighting_json: string
  visible_entities_json: string
  t2i_prompt_cinematic: string | null
  t2i_prompt_closeup: string | null
  t2i_composer_version: string | null
  resolved_entities?: ResolvedEntity[]
  dependent_scenes?: Array<{
    still_id: string
    still_index: number
    beat_title: string
    has_image: boolean
    image_id: string | null
  }>
  variations?: Array<{
    label: string
    type: string
    angle: string | null
    color: string | null
    reason: string | null
  }>
  status: string
  created_at: 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
  prompt_type: string | null
  created_at: string
  variant_type?: string | null
  is_recommended?: boolean
  recommendation_reason?: string | null
  angle_horizontal?: number | null
  angle_vertical?: number | null
  angle_zoom?: number | null
  color_prompt?: string | null
  selected_for_pdf?: boolean
}

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

export function EpisodeDetail() {
  const { id, episodeId } = useParams<{ id: string; episodeId: string }>()
  const { t } = useI18n()
  const navigate = useNavigate()
  const { addToast } = useToast()

  const [episode, setEpisode] = useState<Episode | null>(null)
  const [loading, setLoading] = useState(true)

  const [stills, setStills] = useState<SceneStill[]>([])
  const [stillsLoading, setStillsLoading] = useState(false)
  const [stillImages, setStillImages] = useState<Record<string, ImageAsset[]>>({})

  const [generatingStillId, setGeneratingStillId] = useState<string | null>(null)
  // Track stills whose scene description was edited (need T2I reconversion)
  const [, setT2iNeedsReconvert] = useState<Set<string>>(new Set())

  const [entities, setEntities] = useState<Entity[]>([])
  const [entitiesLoading, setEntitiesLoading] = useState(false)
  const [generatingImages, setGeneratingImages] = useState(false)

  // Screenplay fulltext for segment display
  const [screenplayText, setScreenplayText] = useState<string | null>(null)
  const [segmentContextChars, setSegmentContextChars] = useState(100)

  // WorldGuide state
  const [worldGuide, setWorldGuide] = useState<any>(null)
  const [showWorldGuide, setShowWorldGuide] = useState(false)
  const [editingWorldGuide, setEditingWorldGuide] = useState(false)
  const [wgEditText, setWgEditText] = useState('')
  const [wgJsonMode, setWgJsonMode] = useState(false)

  // Scene segmentation state
  const [segmentPreview, setSegmentPreview] = useState<{
    base_scenes: number; split_candidates: number; estimated_total: number; threshold: number;
    scenes: Array<{ index: number; heading: string; length: number; will_split: boolean }>
  } | null>(null)
  const [splitThreshold, setSplitThreshold] = useState(600)
  const [showSegmentation, setShowSegmentation] = useState(false)
  const [reanalyzing, setReanalyzing] = useState(false)

  // Variation state
  const [recommendingStillId, setRecommendingStillId] = useState<string | null>(null)
  const [generatingVariationsStillId, setGeneratingVariationsStillId] = useState<string | null>(null)
  const [editingAngle, setEditingAngle] = useState(false)
  const [editingColor, setEditingColor] = useState(false)
  const [i2iLoading, setI2iLoading] = useState(false)

  const [progress, setProgress] = useState<AllProgress | null>(null)
  const progressPollRef = useRef<ReturnType<typeof setInterval> | null>(null)
  const segmentDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  // Generation status for resume/full buttons
  const [genStatus, setGenStatus] = useState<{
    ref_total: number; ref_done: number; ref_failed: number;
    scene_total: number; scene_done: number; scene_failed: number;
    scene_primary: number; fal_count: number;
  } | null>(null)

  const fetchProgress = useCallback(async () => {
    if (!id || !episodeId) return
    try {
      const data = await api<AllProgress>(`/api/v1/projects/${id}/episodes/${episodeId}/progress`)
      setProgress(data)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '진행 상태 조회에 실패했습니다')
    }
  }, [id, episodeId, addToast])

  const fetchWorldGuide = useCallback(async () => {
    if (!id) return
    try {
      const data = await api<any>(`/api/v1/projects/${id}/style-rules`)
      setWorldGuide(data)
      if (data.scene_split_threshold) setSplitThreshold(data.scene_split_threshold)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '월드가이드 조회에 실패했습니다')
    }
  }, [id, addToast])

  const fetchScreenplayText = useCallback(async () => {
    if (!id || !episodeId) return
    try {
      const data = await api<{ fulltext: string; segment_context_chars: number }>(
        `/api/v1/projects/${id}/episodes/${episodeId}/fulltext`
      )
      setScreenplayText(data.fulltext)
      setSegmentContextChars(data.segment_context_chars)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '시나리오 원문 조회에 실패했습니다')
    }
  }, [id, episodeId, addToast])

  const fetchSegmentPreview = useCallback(async (threshold?: number) => {
    if (!id || !episodeId) return
    try {
      const th = threshold ?? splitThreshold
      const data = await api<any>(`/api/v1/projects/${id}/episodes/${episodeId}/segment-preview?threshold=${th}`)
      setSegmentPreview(data)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '세그먼트 미리보기에 실패했습니다')
    }
  }, [id, episodeId, splitThreshold, addToast])

  const fetchGenStatus = useCallback(async () => {
    if (!id || !episodeId) return
    try {
      const data = await api<{ ref_total: number; ref_done: number; scene_total: number; scene_done: number }>(
        `/api/v1/projects/${id}/episodes/${episodeId}/generation-status`
      )
      setGenStatus(data)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '생성 상태 조회에 실패했습니다')
    }
  }, [id, episodeId, addToast])

  const fetchEpisode = useCallback(async () => {
    if (!id || !episodeId) return
    setLoading(true)
    try {
      const data = await api<Episode>(`/api/v1/projects/${id}/episodes/${episodeId}`)
      setEpisode(data)
    } catch {
      setEpisode(null)
    } finally {
      setLoading(false)
    }
  }, [id, episodeId])

  const fetchStills = useCallback(async (preserveScroll = false) => {
    if (!id || !episodeId) return
    const scrollY = preserveScroll ? window.scrollY : 0
    if (!preserveScroll) setStillsLoading(true)
    try {
      const data = await api<SceneStill[]>(`/api/v1/projects/${id}/episodes/${episodeId}/stills`)
      setStills(data)
      if (preserveScroll) {
        requestAnimationFrame(() => window.scrollTo(0, scrollY))
      }
    } catch {
      if (!preserveScroll) setStills([])
    } finally {
      if (!preserveScroll) setStillsLoading(false)
    }
  }, [id, episodeId])

  const fetchStillImages = useCallback(async (stillId: string) => {
    if (!id) return
    try {
      const data = await api<ImageAsset[]>(`/api/v1/projects/${id}/images?still_id=${stillId}`)
      setStillImages(prev => ({ ...prev, [stillId]: data }))
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '이미지 조회에 실패했습니다')
    }
  }, [id, addToast])

  const fetchAllStillImages = useCallback(async (stillList: SceneStill[]) => {
    if (!id) return
    for (const still of stillList) {
      fetchStillImages(still.id)
    }
  }, [id, fetchStillImages])

  const fetchEntities = useCallback(async () => {
    if (!id) return
    setEntitiesLoading(true)
    try {
      const data = await api<Entity[]>(`/api/v1/projects/${id}/entities`)
      setEntities(data)
    } catch {
      setEntities([])
    } finally {
      setEntitiesLoading(false)
    }
  }, [id])

  useEffect(() => {
    fetchEpisode()
    fetchProgress()
    fetchGenStatus()
    fetchWorldGuide()
    fetchScreenplayText()
  }, [fetchEpisode, fetchProgress, fetchGenStatus, fetchWorldGuide, fetchScreenplayText])

  // Track previous running state to detect transitions to completed/error
  const prevRunningRef = useRef(false)

  // Poll progress while any operation is running
  useEffect(() => {
    const hasRunning = progress && Object.values(progress).some(
      op => op && op.status === 'running'
    )

    // Detect transition: was running, now completed/error -> refresh data
    if (prevRunningRef.current && !hasRunning && !generatingImages) {
      fetchStills(true)
      fetchGenStatus()
      fetchEntities()
    }
    prevRunningRef.current = !!(hasRunning || generatingImages)

    if (hasRunning || generatingImages) {
      if (!progressPollRef.current) {
        progressPollRef.current = setInterval(() => {
          fetchProgress()
        }, 3000)
      }
    } else {
      if (progressPollRef.current) {
        clearInterval(progressPollRef.current)
        progressPollRef.current = null
      }
    }
    return () => {
      if (progressPollRef.current) {
        clearInterval(progressPollRef.current)
        progressPollRef.current = null
      }
    }
  }, [progress, generatingImages, fetchProgress, fetchStills, fetchGenStatus, fetchEntities])

  useEffect(() => {
    fetchStills()
    // entities는 씬 카드의 참조 요소 추가에 필요
    if (entities.length === 0 && !entitiesLoading) fetchEntities()
  }, [fetchStills, fetchEntities, entities.length, entitiesLoading])

  // Detect stills needing T2I reconversion from server data
  useEffect(() => {
    const needsReconvert = new Set<string>()
    for (const still of stills) {
      if (still.still_frame_prompt && !still.t2i_prompt_cinematic) {
        needsReconvert.add(still.id)
      }
    }
    if (needsReconvert.size > 0) {
      setT2iNeedsReconvert(prev => new Set([...prev, ...needsReconvert]))
    }
  }, [stills])

  // Fetch images when stills change
  useEffect(() => {
    if (stills.length > 0) {
      fetchAllStillImages(stills)
    }
  }, [stills, fetchAllStillImages])




  // --- Variation handlers ---

  const handleRecommendVariations = async (stillId: string) => {
    if (!id) return
    setRecommendingStillId(stillId)
    try {
      await api(`/api/v1/projects/${id}/stills/${stillId}/recommend-variations`, {
        method: 'POST',
      })
      await fetchStillImages(stillId)
      addToast('success', '변형 추천이 완료되었습니다')
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '변형 추천에 실패했습니다')
    } finally {
      setRecommendingStillId(null)
    }
  }

  const handleGenerateWithVariations = async (stillId: string) => {
    if (!id) return
    setGeneratingVariationsStillId(stillId)
    try {
      await api(`/api/v1/projects/${id}/stills/${stillId}/generate-with-variations`, {
        method: 'POST',
      })
      await fetchStillImages(stillId)
      addToast('success', '변형 이미지 생성이 완료되었습니다')
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '변형 이미지 생성에 실패했습니다')
    } finally {
      setGeneratingVariationsStillId(null)
    }
  }

  const handleEditAngle = async (imageId: string, h: number, v: number, z: number) => {
    if (!id) return
    setEditingAngle(true)
    try {
      await api(`/api/v1/projects/${id}/images/${imageId}/edit-angle`, {
        method: 'POST',
        body: JSON.stringify({ horizontal: h, vertical: v, zoom: z }),
      })
      // Refresh all still images to pick up the edited image
      await fetchAllStillImages(stills)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '앵글 편집에 실패했습니다')
    } finally {
      setEditingAngle(false)
    }
  }

  const handleEditColor = async (imageId: string, prompt: string) => {
    if (!id) return
    setEditingColor(true)
    try {
      await api(`/api/v1/projects/${id}/images/${imageId}/edit-color`, {
        method: 'POST',
        body: JSON.stringify({ color_prompt: prompt }),
      })
      await fetchAllStillImages(stills)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '색감 편집에 실패했습니다')
    } finally {
      setEditingColor(false)
    }
  }

  const handleApplyAngleColor = async (imageId: string, h: number, v: number, z: number, _colorPrompt: string) => {
    if (!id) return
    setI2iLoading(true)
    try {
      await api(`/api/v1/projects/${id}/images/${imageId}/edit-angle`, {
        method: 'POST',
        body: JSON.stringify({ horizontal: h, vertical: v, zoom: z }),
        timeoutMs: 120000,
      })
      await fetchAllStillImages(stills)
      addToast('success', 'fal.ai 앵글 편집이 완료되었습니다')
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '앵글 적용에 실패했습니다')
    } finally {
      setI2iLoading(false)
    }
  }

  const handleRegenerateWithPrompt = async (stillId: string, customPrompt: string) => {
    if (!id) return
    setI2iLoading(true)
    try {
      await api(`/api/v1/projects/${id}/stills/${stillId}/generate-image`, {
        method: 'POST',
        body: JSON.stringify({ custom_prompt: customPrompt }),
        timeoutMs: 120000,
      })
      await fetchStillImages(stillId)
      addToast('success', '프롬프트로 이미지가 재생성되었습니다')
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '이미지 재생성에 실패했습니다')
    } finally {
      setI2iLoading(false)
    }
  }

  const handleSetRepresentative = async (stillId: string, imageId: string) => {
    if (!id) return
    try {
      await api(`/api/v1/projects/${id}/images/${imageId}/set-primary`, {
        method: 'POST',
      })
      await fetchStillImages(stillId)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '대표 이미지 설정에 실패했습니다')
    }
  }

  const handleSelectVariant = async (stillId: string, variant: string) => {
    if (!id) return
    try {
      await api(`/api/v1/projects/${id}/stills/${stillId}/select-variant`, {
        method: 'PATCH',
        body: JSON.stringify({ variant }),
      })
      await fetchStillImages(stillId)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '변형 선택에 실패했습니다')
    }
  }

  const handleSelectOriginal = async (stillId: string, imageId: string) => {
    if (!id) return
    try {
      await api(`/api/v1/projects/${id}/stills/${stillId}/select-original`, {
        method: 'PATCH',
        body: JSON.stringify({ image_id: imageId }),
      })
      await fetchStillImages(stillId)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '원본 선택에 실패했습니다')
    }
  }

  const handleRegenerateVariant = async (stillId: string, imageId: string) => {
    if (!id) return
    setGeneratingStillId(stillId)
    try {
      await api(`/api/v1/projects/${id}/images/${imageId}/regenerate`, {
        method: 'POST',
      })
      await fetchStillImages(stillId)
      addToast('success', '이미지 재생성이 완료되었습니다')
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '이미지 재생성에 실패했습니다')
    } finally {
      setGeneratingStillId(null)
    }
  }


  const handleRemoveEntity = async (stillId: string, entityId: string) => {
    if (!id) return
    try {
      const still = stills.find(s => s.id === stillId)
      if (!still) return
      const visList = JSON.parse(still.visible_entities_json || '[]')
      const removing = visList.find((v: any) => v.entity_id === entityId)
      const removingName = removing?.entity_name || ''
      const filtered = visList.filter((v: any) => {
        if (typeof v === 'string') return v !== entityId
        if (typeof v === 'object') return v.entity_id !== entityId
        return true
      })
      // T2I: [[이름]] → [이름: 설명] 변환
      let t2i = still.t2i_prompt_cinematic || ''
      if (removingName) {
        const escaped = removingName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
        const ent = entities.find(e => e.id === entityId)
        const desc = ent?.description?.substring(0, 50) || ''
        t2i = t2i.replace(new RegExp(`\\[\\[${escaped}\\]\\]`, 'g'), desc ? `[${removingName}: ${desc}]` : removingName)
      }
      const body: any = { visible_entities_json: JSON.stringify(filtered) }
      if (t2i !== still.t2i_prompt_cinematic) body.t2i_prompt_cinematic = t2i
      await api(`/api/v1/projects/${id}/stills/${stillId}`, { method: 'PATCH', body: JSON.stringify(body) })
      await fetchStills(true)
    } catch (e) { console.error('removeEntity failed:', e) }
  }

  const handleAddEntity = async (stillId: string, entityId: string) => {
    if (!id) return
    try {
      const still = stills.find(s => s.id === stillId)
      if (!still) return
      const visList = JSON.parse(still.visible_entities_json || '[]')
      const exists = visList.some((v: any) =>
        (typeof v === 'object' && v.entity_id === entityId) || v === entityId
      )
      const ent = entities.find(e => e.id === entityId)
      const entName = ent?.name || ''
      // visible_entities에 추가 (아직 없으면)
      if (!exists) {
        visList.push({ entity_id: entityId, entity_name: entName, entity_type: ent?.entity_type || '' })
      }
      // T2I: [이름: 설명] → [[이름]] 변환, 또는 프롬프트 끝에 [[이름]] 추가
      let t2i = still.t2i_prompt_cinematic || ''
      const escaped = entName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
      const unlinkedPattern = new RegExp(`\\[${escaped}\\s*:[^\\]]*\\]`, 'g')
      if (unlinkedPattern.test(t2i)) {
        // [이름: 설명] → [[이름]]
        t2i = t2i.replace(unlinkedPattern, `[[${entName}]]`)
      } else if (!t2i.includes(`[[${entName}]]`)) {
        // 프롬프트에 없으면 끝에 추가
        t2i = t2i.trimEnd() + `, [[${entName}]]`
      }
      const body: any = { visible_entities_json: JSON.stringify(visList) }
      if (t2i !== still.t2i_prompt_cinematic) body.t2i_prompt_cinematic = t2i
      await api(`/api/v1/projects/${id}/stills/${stillId}`, { method: 'PATCH', body: JSON.stringify(body) })
      await fetchStills(true)
    } catch (e) { console.error('addEntity failed:', e) }
  }

  const handleSaveVariationPrompt = async (stillId: string, prompt: string) => {
    if (!id) return
    try {
      await api(`/api/v1/projects/${id}/stills/${stillId}`, {
        method: 'PATCH',
        body: JSON.stringify({ still_frame_prompt: prompt }),
      })
      // P2 fix: 변형 뷰에서 씬 편집 시에도 T2I 재변환 필요 표시
      setT2iNeedsReconvert(prev => new Set(prev).add(stillId))
      await fetchStills(true)
    } catch (err: any) {
      addToast('error', err?.message || err?.detail || '프롬프트 저장에 실패했습니다')
    }
  }

  // Helper: group images into {original, variant_a, variant_b, ...}
  const groupVariationImages = (images: ImageAsset[]): Record<string, ImageAsset | null> => {
    let original: ImageAsset | null = null
    const variantMap: Record<string, ImageAsset> = {}

    for (const img of images) {
      if (img.variant_type && img.variant_type.startsWith('variant_')) {
        const existing = variantMap[img.variant_type]
        if (!existing || img.created_at > existing.created_at) {
          variantMap[img.variant_type] = img
        }
      } else {
        if (!original || img.is_primary || (!original.is_primary && img.created_at > original.created_at)) {
          original = img
        }
      }
    }

    return { original, ...variantMap }
  }

  if (loading) {
    return (
      <AppShell title={t('common.loading')} projectId={id}>
        <div className="empty-state">{t('common.loading')}</div>
      </AppShell>
    )
  }

  if (!episode) {
    return (
      <AppShell title={t('episode.detail')} projectId={id}>
        <div className="empty-state">{t('common.no_data')}</div>
      </AppShell>
    )
  }

  return (
    <AppShell title={`EP${episode.episode_number} — ${episode.title}`} projectId={id}>
      <div className="page">
        {/* Episode info bar */}
        <div className="info-bar">
          <div className="info-item">
            <span className="info-label">{t('episode.number')}</span>
            <span className="info-value" style={{ fontFamily: 'var(--font-mono)', fontWeight: 700 }}>
              EP{episode.episode_number}
            </span>
          </div>
          <div className="info-item">
            <span className="info-label">{t('project.status')}</span>
            <span className="info-value">
              <Badge label={episode.status} status={episode.status} />
            </span>
          </div>
          {episode.page_count != null && (
            <div className="info-item">
              <span className="info-label">{t('episode.pages')}</span>
              <span className="info-value">{episode.page_count}</span>
            </div>
          )}
          <div className="info-item">
            <span className="info-label">{t('episode.language')}</span>
            <span className="info-value">{episode.language.toUpperCase()}</span>
          </div>
          <div className="info-item">
            <span className="info-label">{t('episode.source')}</span>
            <span className="info-value truncate" style={{ maxWidth: '200px', display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {episode.source_filename}
            </span>
          </div>
        </div>

        {/* Pipeline progress panel */}
        {progress && Object.values(progress).some(op => op !== null) && (
          <PipelineProgressPanel progress={progress} />
        )}

        {/* WorldGuide + 스타일 규칙 (접이식) */}
        {worldGuide && worldGuide.style_rules && (
          <div className="card" style={{ padding: '12px 16px', marginBottom: '12px' }}>
            <div
              style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
              onClick={() => setShowWorldGuide(!showWorldGuide)}
            >
              <span style={{ fontSize: '12px', fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
                {t('worldguide.title')}
              </span>
              <span style={{ fontSize: '11px', color: 'var(--text-dim)' }}>{showWorldGuide ? '접기' : '펼치기'}</span>
            </div>
            {showWorldGuide && (
              <div style={{ marginTop: '10px' }}>
                {/* 에피소드 요약 */}
                {episode?.summary && (
                  <div style={{ marginBottom: '8px' }}>
                    <span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)' }}>에피소드 요약</span>
                    <div style={{ fontSize: '12px', color: 'var(--text-muted)', lineHeight: 1.6, marginTop: '2px' }}>
                      {episode.summary}
                    </div>
                  </div>
                )}
                {/* 스타일 규칙 */}
                {worldGuide.style_rules && (
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '8px' }}>
                    <div>
                      <span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)' }}>설정</span>
                      <div style={{ fontSize: '11px', color: 'var(--text-muted)', lineHeight: 1.6, marginTop: '2px' }}>
                        {worldGuide.style_rules.era && <div>시대: {worldGuide.style_rules.era}</div>}
                        {worldGuide.style_rules.region && <div>지역: {worldGuide.style_rules.region}</div>}
                        {(worldGuide.style_rules.genre || worldGuide.style_rules.genre_tone) && <div>장르: {worldGuide.style_rules.genre || worldGuide.style_rules.genre_tone}</div>}
                        {(worldGuide.style_rules.color_tone || worldGuide.style_rules.color_mood) && <div>색감: {worldGuide.style_rules.color_tone || worldGuide.style_rules.color_mood}</div>}
                        {worldGuide.style_rules.building_style && <div>건축: {worldGuide.style_rules.building_style}</div>}
                        {worldGuide.style_rules.clothing_style && <div>복장: {worldGuide.style_rules.clothing_style}</div>}
                        {worldGuide.style_rules.vehicle_style && <div>차량: {worldGuide.style_rules.vehicle_style}</div>}
                      </div>
                    </div>
                    <div>
                      {worldGuide.style_rules.must_avoid && (
                        <>
                          <span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--red, #ef4444)' }}>금지</span>
                          <div style={{ fontSize: '11px', color: 'var(--text-muted)', lineHeight: 1.6, marginTop: '2px' }}>
                            {Array.isArray(worldGuide.style_rules.must_avoid)
                              ? worldGuide.style_rules.must_avoid.map((r: string, i: number) => <div key={i}>- {r}</div>)
                              : <div>- {worldGuide.style_rules.must_avoid}</div>
                            }
                          </div>
                        </>
                      )}
                      {worldGuide.style_rules.must_maintain && (
                        <>
                          <span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--green)', marginTop: '6px', display: 'block' }}>유지</span>
                          <div style={{ fontSize: '11px', color: 'var(--text-muted)', lineHeight: 1.6, marginTop: '2px' }}>
                            {Array.isArray(worldGuide.style_rules.must_maintain)
                              ? worldGuide.style_rules.must_maintain.map((r: string, i: number) => <div key={i}>- {r}</div>)
                              : <div>- {worldGuide.style_rules.must_maintain}</div>
                            }
                          </div>
                        </>
                      )}
                    </div>
                  </div>
                )}
                {/* 수정 버튼 */}
                {!editingWorldGuide ? (
                  <Button size="sm" variant="ghost" onClick={() => {
                    setEditingWorldGuide(true)
                    setWgJsonMode(false)
                    setWgEditText(JSON.stringify(worldGuide.style_rules || {}, null, 2))
                  }}>
                    {t('btn.edit')}
                  </Button>
                ) : (
                  <div>
                    {!wgJsonMode ? (
                      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: '8px' }}>
                        <div>
                          <label style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)', display: 'block', marginBottom: '2px' }}>시대 (era)</label>
                          <input
                            type="text"
                            value={(() => { try { return JSON.parse(wgEditText).era || '' } catch { return '' } })()}
                            onChange={(e) => { try { const obj = JSON.parse(wgEditText); obj.era = e.target.value; setWgEditText(JSON.stringify(obj, null, 2)) } catch {} }}
                            style={{ width: '100%', padding: '4px 8px', borderRadius: '6px', border: '1px solid #334155', background: '#1e293b', color: '#e2e8f0', fontSize: '12px' }}
                          />
                        </div>
                        <div>
                          <label style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)', display: 'block', marginBottom: '2px' }}>지역 (region)</label>
                          <input
                            type="text"
                            value={(() => { try { return JSON.parse(wgEditText).region || '' } catch { return '' } })()}
                            onChange={(e) => { try { const obj = JSON.parse(wgEditText); obj.region = e.target.value; setWgEditText(JSON.stringify(obj, null, 2)) } catch {} }}
                            style={{ width: '100%', padding: '4px 8px', borderRadius: '6px', border: '1px solid #334155', background: '#1e293b', color: '#e2e8f0', fontSize: '12px' }}
                          />
                        </div>
                        <div>
                          <label style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)', display: 'block', marginBottom: '2px' }}>장르 (genre)</label>
                          <input
                            type="text"
                            value={(() => { try { return JSON.parse(wgEditText).genre || '' } catch { return '' } })()}
                            onChange={(e) => { try { const obj = JSON.parse(wgEditText); obj.genre = e.target.value; setWgEditText(JSON.stringify(obj, null, 2)) } catch {} }}
                            style={{ width: '100%', padding: '4px 8px', borderRadius: '6px', border: '1px solid #334155', background: '#1e293b', color: '#e2e8f0', fontSize: '12px' }}
                          />
                        </div>
                        <div>
                          <label style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)', display: 'block', marginBottom: '2px' }}>색감 (color_tone)</label>
                          <input
                            type="text"
                            value={(() => { try { return JSON.parse(wgEditText).color_tone || '' } catch { return '' } })()}
                            onChange={(e) => { try { const obj = JSON.parse(wgEditText); obj.color_tone = e.target.value; setWgEditText(JSON.stringify(obj, null, 2)) } catch {} }}
                            style={{ width: '100%', padding: '4px 8px', borderRadius: '6px', border: '1px solid #334155', background: '#1e293b', color: '#e2e8f0', fontSize: '12px' }}
                          />
                        </div>
                        <div style={{ gridColumn: '1 / -1' }}>
                          <label style={{ fontSize: '10px', fontWeight: 600, color: 'var(--text-dim)', display: 'block', marginBottom: '2px' }}>에피소드 요약 (episode_summary)</label>
                          <textarea
                            rows={3}
                            value={(() => { try { return JSON.parse(wgEditText).episode_summary || '' } catch { return '' } })()}
                            onChange={(e) => { try { const obj = JSON.parse(wgEditText); obj.episode_summary = e.target.value; setWgEditText(JSON.stringify(obj, null, 2)) } catch {} }}
                            style={{ width: '100%', padding: '4px 8px', borderRadius: '6px', border: '1px solid #334155', background: '#1e293b', color: '#e2e8f0', fontSize: '12px', resize: 'vertical' }}
                          />
                        </div>
                      </div>
                    ) : (
                      <textarea
                        rows={8}
                        value={wgEditText}
                        onChange={(e) => setWgEditText(e.target.value)}
                        style={{ width: '100%', fontSize: '11px', fontFamily: 'var(--font-mono)', marginBottom: '8px' }}
                      />
                    )}
                    <div style={{ display: 'flex', gap: '6px', marginTop: '4px' }}>
                      <Button size="sm" onClick={async () => {
                        try {
                          const parsed = JSON.parse(wgEditText)
                          await api(`/api/v1/projects/${id}/style-rules`, {
                            method: 'PATCH',
                            body: JSON.stringify({ style_rules: parsed }),
                          })
                          await fetchWorldGuide()
                          setEditingWorldGuide(false)
                          addToast('success', '스타일 규칙이 저장되었습니다')
                        } catch (err: any) {
                          addToast('error', err?.message || err?.detail || '스타일 규칙 저장에 실패했습니다')
                        }
                      }}>{t('btn.save')}</Button>
                      <Button size="sm" variant="ghost" onClick={() => setEditingWorldGuide(false)}>{t('btn.cancel')}</Button>
                      <Button size="sm" variant="ghost" onClick={() => setWgJsonMode(!wgJsonMode)}>
                        {wgJsonMode ? '구조화 편집' : '고급 JSON 편집'}
                      </Button>
                    </div>
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* Scene Segmentation Settings */}
        {episode.status === 'analyzed' && (
          <div className="card" style={{ padding: '12px 16px', marginBottom: '12px' }}>
            <div
              style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }}
              onClick={() => { setShowSegmentation(!showSegmentation); if (!showSegmentation && !segmentPreview) fetchSegmentPreview() }}
            >
              <span style={{ fontSize: '12px', fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em' }}>
                씬 세그먼테이션
              </span>
              <span style={{ fontSize: '11px', color: 'var(--text-dim)' }}>
                {segmentPreview ? `${segmentPreview.estimated_total}씬 예상` : ''} {showSegmentation ? '접기' : '펼치기'}
              </span>
            </div>
            {showSegmentation && (
              <div style={{ marginTop: '10px' }}>
                {/* Threshold 설정 */}
                <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '8px' }}>
                  <span style={{ fontSize: '11px', color: 'var(--text-dim)', whiteSpace: 'nowrap' }}>분할 임계값</span>
                  <input
                    type="range"
                    min={300}
                    max={1200}
                    step={50}
                    value={splitThreshold}
                    onChange={(e) => {
                      const v = Number(e.target.value)
                      setSplitThreshold(v)
                      if (segmentDebounceRef.current) clearTimeout(segmentDebounceRef.current)
                      segmentDebounceRef.current = setTimeout(() => fetchSegmentPreview(v), 400)
                    }}
                    style={{ flex: 1 }}
                  />
                  <span style={{ fontSize: '12px', fontWeight: 600, color: 'var(--text-primary)', minWidth: '45px' }}>
                    {splitThreshold}자
                  </span>
                </div>

                {/* 미리보기 결과 */}
                {segmentPreview && (
                  <div style={{ marginBottom: '10px' }}>
                    <div style={{ display: 'flex', gap: '16px', fontSize: '11px', color: 'var(--text-muted)', marginBottom: '6px' }}>
                      <span>원본 씬: <strong>{segmentPreview.base_scenes}</strong></span>
                      <span>분할 대상: <strong style={{ color: 'var(--warning, #d97706)' }}>{segmentPreview.split_candidates}</strong></span>
                      <span>예상 총 씬: <strong style={{ color: 'var(--green, #22c55e)' }}>{segmentPreview.estimated_total}</strong></span>
                    </div>
                    {/* 씬 목록 (분할 대상만 하이라이트) */}
                    <div style={{ maxHeight: '200px', overflowY: 'auto', fontSize: '10px', lineHeight: 1.8, fontFamily: 'var(--font-mono)' }}>
                      {segmentPreview.scenes.map((s) => (
                        <div key={s.index} style={{
                          color: s.will_split ? 'var(--warning, #d97706)' : 'var(--text-dim)',
                          fontWeight: s.will_split ? 600 : 400,
                        }}>
                          {s.will_split ? '◆' : '·'} {s.index}. {s.heading} ({s.length}자)
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                {/* 저장 + 재분석 버튼 */}
                <div style={{ display: 'flex', gap: '6px' }}>
                  <Button size="sm" variant="secondary" onClick={async () => {
                    await api(`/api/v1/projects/${id}/style-rules`, {
                      method: 'PATCH',
                      body: JSON.stringify({ scene_split_threshold: splitThreshold }),
                    })
                  }}>
                    설정 저장
                  </Button>
                  <Button size="sm" disabled={reanalyzing} onClick={async () => {
                    // 먼저 threshold 저장
                    await api(`/api/v1/projects/${id}/style-rules`, {
                      method: 'PATCH',
                      body: JSON.stringify({ scene_split_threshold: splitThreshold }),
                    })
                    setReanalyzing(true)
                    try {
                      await api(`/api/v1/projects/${id}/episodes/${episodeId}/reanalyze-scenes`, { method: 'POST' })
                      fetchProgress()
                      addToast('success', '씬 재분석이 시작되었습니다')
                    } catch (err: any) { addToast('error', err?.message || err?.detail || '씬 재분석에 실패했습니다') } finally {
                      setReanalyzing(false)
                    }
                  }}>
                    {reanalyzing ? '재분석 중...' : `씬 재분석 (${segmentPreview?.estimated_total ?? '?'}씬)`}
                  </Button>
                </div>
              </div>
            )}
          </div>
        )}

        {/* Pipeline status panel */}
        {episode.status === 'analyzed' && genStatus && (
          <div style={{ marginBottom: '12px' }}>
            <GenerationStatusPanel
              status={genStatus}
              analysisStatus={episode.status}
              onRetryRef={async () => {
                try {
                  await api(`/api/v1/projects/${id}/episodes/${episodeId}/generate-reference-images?mode=resume`, { method: 'POST' })
                  fetchProgress()
                  addToast('success', '참조 이미지 재시도가 시작되었습니다')
                } catch (err: any) { addToast('error', err?.message || '참조 이미지 생성에 실패했습니다') }
              }}
              onRetryScene={async () => {
                try {
                  await api(`/api/v1/projects/${id}/episodes/${episodeId}/generate-images?mode=resume`, { method: 'POST' })
                  fetchProgress()
                  addToast('success', '씬 이미지 재시도가 시작되었습니다')
                } catch (err: any) { addToast('error', err?.message || '씬 이미지 생성에 실패했습니다') }
              }}
              loading={generatingImages}
            />
          </div>
        )}

        {/* LLM Model Config */}
        {id && (
          <div style={{ marginBottom: '12px' }}>
            <LLMConfigPanel projectId={id} />
          </div>
        )}

        {/* Action buttons */}
        {episode.status === 'analyzed' && (() => {
          // 생성 중이면 progress에서 running 상태 확인 (새로고침해도 유지)
          const isImageGenRunning = !!(progress?.image_generation?.status === 'running')
          const isRefGenRunning = !!(progress?.reference_image_generation?.status === 'running')
          const isAnyRunning = generatingImages || isImageGenRunning || isRefGenRunning
          return (
          <div style={{ display: 'flex', gap: '10px', marginBottom: '16px', alignItems: 'center' }}>
            {isAnyRunning && (
              <Badge label={t('common.loading')} variant="orange" />
            )}
            {genStatus && genStatus.ref_done === 0 && genStatus.ref_total > 0 && !isAnyRunning && (
              <span style={{ fontSize: '12px', color: 'var(--warning, #d97706)' }}>
                {t('image.ref_required')}
              </span>
            )}
            {/* 씬 이미지: 미완료면 이어서 + 전체, 완료면 전체만 */}
            {genStatus && genStatus.scene_done > 0 && genStatus.scene_done < genStatus.scene_total && (
              <Button
                size="sm"
                disabled={isAnyRunning}
                onClick={async () => {
                  setGeneratingImages(true)
                  try {
                    await api(`/api/v1/projects/${id}/episodes/${episodeId}/generate-images?mode=resume`, { method: 'POST' })
                    fetchProgress()
                    addToast('success', '이미지 생성이 시작되었습니다')
                  } catch (err: any) {
                    addToast('error', err?.message || err?.detail || '이미지 생성에 실패했습니다')
                  } finally {
                    setGeneratingImages(false)
                  }
                }}
              >
                {t('image.resume_generate')} ({genStatus.scene_done}/{genStatus.scene_total})
              </Button>
            )}
            <Button
              size="sm"
              variant={genStatus && genStatus.scene_done > 0 && genStatus.scene_done < genStatus.scene_total ? 'secondary' : 'primary'}
              disabled={isAnyRunning}
              onClick={async () => {
                setGeneratingImages(true)
                try {
                  await api(`/api/v1/projects/${id}/episodes/${episodeId}/generate-images?mode=full`, { method: 'POST' })
                  fetchProgress()
                  addToast('success', '이미지 생성이 시작되었습니다')
                } catch (err: any) {
                  addToast('error', err?.message || err?.detail || '이미지 생성에 실패했습니다')
                } finally {
                  setGeneratingImages(false)
                }
              }}
            >
              {t('image.generate')}
            </Button>
            <Button
              variant="secondary"
              size="sm"
              onClick={() => navigate(`/projects/${id}/export`)}
            >
              {t('export.generate_webbook')}
            </Button>
          </div>
          )
        })()}

        {/* Scene Stills */}
        <div>
          {(
            <div>
              {/* v5: 일괄 변형 추천+생성 버튼 제거 — N개 원본 생성으로 대체 */}

              {/* Scene jump navigation */}
              {stills.length > 0 && (
                <div style={{ marginBottom: '10px', display: 'flex', alignItems: 'center', gap: '8px' }}>
                  <span style={{ fontSize: '11px', color: 'var(--text-dim)' }}>씬 이동</span>
                  <input
                    type="number"
                    min={1}
                    max={stills.length}
                    placeholder="씬 #"
                    style={{
                      width: '60px', padding: '4px 8px', borderRadius: '6px',
                      border: '1px solid #334155', background: '#1e293b',
                      color: '#e2e8f0', fontSize: '13px',
                    }}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') {
                        const idx = parseInt((e.target as HTMLInputElement).value)
                        const el = document.getElementById(`scene-${idx}`)
                        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
                      }
                    }}
                  />
                  <span style={{ fontSize: '11px', color: 'var(--text-dim)' }}>/ {stills.length}</span>
                </div>
              )}


              {stillsLoading ? (
                <div className="empty-state">{t('common.loading')}</div>
              ) : stills.length === 0 ? (
                <div className="empty-state">{t('still.no_stills')}</div>
              ) : (
                <div style={{ display: 'grid', gap: '14px' }}>
                  {stills.map((still) => {
                    const images = stillImages[still.id] || []
                    const grouped = groupVariationImages(images)

                    return (
                      <div key={still.id} id={`scene-${still.still_index}`}>
                      <SceneVariationCard
                        still={still}
                        projectId={id!}
                        images={grouped}
                        allImages={images}
                        screenplayText={screenplayText}
                        segmentContextChars={segmentContextChars}
                        onRecommend={() => handleRecommendVariations(still.id)}
                        onGenerate={() => handleGenerateWithVariations(still.id)}
                        onEditAngle={(imageId, h, v, z) => handleEditAngle(imageId, h, v, z)}
                        onEditColor={(imageId, prompt) => handleEditColor(imageId, prompt)}
                        onApplyAngleColor={(imageId, h, v, z, colorPrompt) => handleApplyAngleColor(imageId, h, v, z, colorPrompt)}
                        onSelectVariant={(variant) => handleSelectVariant(still.id, variant)}
                        onSelectOriginal={(imageId) => handleSelectOriginal(still.id, imageId)}
                        onEditPrompt={(prompt) => handleSaveVariationPrompt(still.id, prompt)}
                        onRegenerate={(imageId) => handleRegenerateVariant(still.id, imageId)}
                        onRemoveEntity={(entityId) => handleRemoveEntity(still.id, entityId)}
                        onAddEntity={(entityId) => handleAddEntity(still.id, entityId)}
                        allEntities={entities.map(e => ({ id: e.id, name: e.name, entity_type: e.entity_type }))}
                        onRemoveDependentScene={async () => {
                          try {
                            await api(`/api/v1/projects/${id}/stills/${still.id}`, {
                              method: 'PATCH',
                              body: JSON.stringify({ dependent_scene_id: null }),
                            })
                            await fetchStills(true)
                          } catch (err: any) { addToast('error', err?.message || err?.detail || '작업에 실패했습니다') }
                        }}
                        onSetDependentScene={async (sceneId) => {
                          try {
                            await api(`/api/v1/projects/${id}/stills/${still.id}`, {
                              method: 'PATCH',
                              body: JSON.stringify({ dependent_scene_id: sceneId }),
                            })
                            await fetchStills(true)
                          } catch (err: any) { addToast('error', err?.message || err?.detail || '작업에 실패했습니다') }
                        }}
                        allScenes={stills.map(s => ({ id: s.id, still_index: s.still_index, beat_title: s.beat_title || '' }))}
                        onSaveT2i={async (promptA) => {
                          try {
                            await api(`/api/v1/projects/${id}/stills/${still.id}`, {
                              method: 'PATCH',
                              body: JSON.stringify({ t2i_prompt_cinematic: promptA }),
                            })
                            await fetchStills(true)
                          } catch (err: any) { addToast('error', err?.message || err?.detail || 'T2I 프롬프트 저장에 실패했습니다') }
                        }}
                        onSetRepresentative={(imageId) => handleSetRepresentative(still.id, imageId)}
                        onRegenerateWithPrompt={handleRegenerateWithPrompt}
                        recommendLoading={recommendingStillId === still.id}
                        generateLoading={generatingVariationsStillId === still.id || generatingStillId === still.id}
                        angleLoading={editingAngle}
                        colorLoading={editingColor}
                        isGenerating={!!(progress?.image_generation?.status === 'running')}
                        hasEntityRefs={true}
                        i2iLoading={i2iLoading}
                      />
                      </div>
                    )
                  })}
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </AppShell>
  )
}
