﻿function VideoBadge({ status }) {
  const labelMap = {
    queued: '접수됨',
    rendering: '렌더중',
    rendered: '시안완료',
    approved: '승인',
    scheduled: '예약됨',
    publishing: '발행중',
    published: '게시완료',
    failed: '실패',
  };

  const styleMap = {
    queued: { color: 'var(--fg-muted)', background: 'var(--bg-elevated)' },
    rendering: { color: '#7dd3fc', background: 'rgba(14, 165, 233, 0.14)' },
    rendered: { color: '#34d399', background: 'rgba(16, 185, 129, 0.14)' },
    approved: { color: 'var(--status-approved-fg)', background: 'var(--status-approved-bg)' },
    scheduled: { color: 'var(--status-scheduled-fg)', background: 'var(--status-scheduled-bg)' },
    publishing: { color: '#fbbf24', background: 'rgba(245, 158, 11, 0.16)' },
    published: { color: 'var(--status-published-fg)', background: 'var(--status-published-bg)' },
    failed: { color: 'var(--status-failed-fg)', background: 'var(--status-failed-bg)' },
  };

  return (
    <span className="badge" style={{ ...(styleMap[status] || styleMap.queued) }}>
      <span className="dot" style={{ background: 'currentColor' }} />
      {labelMap[status] || status}
    </span>
  );
}

function getProgressPercent(job) {
  if (job.status === 'rendered') return 100;
  const value = job?.metadata?.progress_percent;
  return typeof value === 'number' ? value : job.status === 'rendering' ? 15 : 0;
}

function getProgressLabel(job) {
  if (job.status === 'rendered') return '시안 생성 완료';
  if (job.status === 'failed') return job.last_error || '렌더 실패';
  return job?.metadata?.progress_label || '렌더 진행 중';
}

function getSelectedAccountIds(job) {
  if (Array.isArray(job?.metadata?.selected_account_ids) && job.metadata.selected_account_ids.length > 0) {
    return job.metadata.selected_account_ids;
  }
  return (window.SAJU.ACCOUNTS || []).map((account) => account.id);
}

function getPlatformCaption(job, platform) {
  const key = `${platform}_caption`;
  const specific = job?.metadata?.[key];
  if (typeof specific === 'string' && specific.trim()) return specific.trim();
  return job.caption || '';
}

function getThumbnailUrl(job) {
  if (typeof job?.metadata?.thumbnail_image_url === 'string' && job.metadata.thumbnail_image_url.trim()) {
    return job.metadata.thumbnail_image_url.trim();
  }
  return null;
}

const HASHTAG_STOPWORDS = new Set([
  'a', 'an', 'and', 'are', 'about', 'before', 'but', 'can', 'did', 'do', 'does', 'ever',
  'feel', 'first', 'for', 'from', 'get', 'had', 'has', 'have', 'how', 'in', 'into', 'is',
  'isn', 'it', 'just', 'life', 'may', 'not', 'of', 'on', 'or', 'our', 'out', 'reason',
  'so', 'something', 'than', 'that', 'the', 'their', 'they', 'this', 'too', 'was', 'were',
  'what', 'when', 'why', 'with', 'won', 'would', 'you', 'your',
]);

function englishOnlyText(value) {
  return String(value || '')
    .replace(/[\u1100-\u11ff\u3130-\u318f\uac00-\ud7af]/g, ' ')
    .replace(/[ \t]{2,}/g, ' ')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

function createHashtags(base, extraTags = []) {
  const words = englishOnlyText(base)
    .toLowerCase()
    .replace(/[\'\'?]/g, '')
    .replace(/[^\p{L}\p{N}\s]/gu, ' ')
    .split(/\s+/)
    .map((word) => word.trim())
    .filter((word) => word.length >= 3)
    .filter((word) => !HASHTAG_STOPWORDS.has(word))
    .filter((word) => !/^\d+$/.test(word))
    .slice(0, 5)
    .map((word) => `#${word}`);
  return [...new Set([...extraTags, ...words])].join(' ');
}

function buildYoutubeCaption(base) {
  const safeBase = englishOnlyText(base) || 'Your birth year has a hidden pattern.';
  const tags = createHashtags(safeBase, ['#shorts', '#saju', '#spirituality']);
  return [safeBase, '', tags].filter(Boolean).join('\n');
}

function buildTiktokCaption(base) {
  const safeBase = englishOnlyText(base) || 'Your birth year has a hidden pattern.';
  const shortBase = safeBase.length > 80 ? `${safeBase.slice(0, 77)}...` : safeBase;
  const tags = createHashtags(safeBase, ['#tiktok', '#saju', '#fyp']);
  return `${shortBase} ${tags}`.trim();
}

function buildInstagramCaption(base) {
  const safeBase = englishOnlyText(base) || 'Your birth year has a hidden pattern.';
  const tags = createHashtags(safeBase, ['#reels', '#saju', '#fortune']);
  return [safeBase, '', tags].filter(Boolean).join('\n');
}

function generatePlatformCaptions(baseCaption) {
  const base = englishOnlyText(baseCaption);
  if (!base) {
    return {
      youtubeCaption: '',
      tiktokCaption: '',
      instagramCaption: '',
    };
  }

  return {
    youtubeCaption: buildYoutubeCaption(base),
    tiktokCaption: buildTiktokCaption(base),
    instagramCaption: buildInstagramCaption(base),
  };
}

function ProgressBar({ job }) {
  const percent = Math.max(0, Math.min(100, getProgressPercent(job)));
  return (
    <div style={{ display: 'grid', gap: 6 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--fg-subtle)' }}>
        <span>{getProgressLabel(job)}</span>
        <span>{percent}%</span>
      </div>
      <div style={{ height: 8, borderRadius: 999, background: 'var(--bg-elevated)', overflow: 'hidden' }}>
        <div
          style={{
            width: `${percent}%`,
            height: '100%',
            borderRadius: 999,
            background: 'linear-gradient(90deg, #38bdf8, #22c55e)',
            transition: 'width 0.35s ease',
          }}
        />
      </div>
    </div>
  );
}

async function fetchVideoJobs() {
  const response = await fetch('/api/admin/social-videos?limit=50', { cache: 'no-store' });
  const data = await response.json().catch(() => null);
  if (!response.ok || data?.error) {
    throw new Error(data?.error || '영상 목록을 불러오지 못했습니다.');
  }
  return Array.isArray(data?.jobs) ? data.jobs : [];
}

async function createVideoJob(payload) {
  const response = await fetch('/api/admin/social-videos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      action: 'create',
      title: payload.title,
      caption: payload.caption,
      image_urls: payload.imageUrls,
      audio_url: payload.audioUrl,
      duration_seconds: payload.durationSeconds,
      platforms: payload.platforms,
      accountIds: payload.accountIds,
      thumbnail_image_url: payload.thumbnailImageUrl,
      youtube_caption: payload.youtubeCaption,
      tiktok_caption: payload.tiktokCaption,
      instagram_caption: payload.instagramCaption,
    }),
  });
  const data = await response.json().catch(() => null);
  if (!response.ok || data?.error) {
    throw new Error(data?.error || '영상 생성 요청 실패');
  }
  return data;
}

async function runVideoAction(jobId, action, extra = {}) {
  if (action === 'publish' && !confirm('이 영상을 즉시 발행하시겠습니까?')) return;

  if (action === 'schedule' && !extra.scheduledFor) {
    const input = prompt('예약 시간을 입력해 주세요. 예: 2026-05-01 21:30');
    if (!input) return;
    const parsed = new Date(input.replace(' ', 'T'));
    if (Number.isNaN(parsed.getTime())) {
      alert('시간 형식이 올바르지 않습니다. 예: 2026-05-01 21:30');
      return;
    }
    extra = { ...extra, scheduledFor: parsed.toISOString() };
  }

  if (action === 'delete' && !confirm('이 영상 작업을 삭제하시겠습니까?')) return;

  const response = await fetch('/api/admin/social-videos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jobId, action, ...extra }),
  });
  const data = await response.json().catch(() => null);
  if (!response.ok || data?.error) {
    throw new Error(data?.error || '영상 작업 처리 실패');
  }
}

function VideoTargetsEditor({ job, onSaved }) {
  const accounts = window.SAJU.ACCOUNTS || [];
  const [accountIds, setAccountIds] = React.useState(() => getSelectedAccountIds(job));
  const [platforms, setPlatforms] = React.useState(() => Array.isArray(job.platforms) ? job.platforms : []);
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    setAccountIds(getSelectedAccountIds(job));
    setPlatforms(Array.isArray(job.platforms) ? job.platforms : []);
  }, [job.id, JSON.stringify(job.metadata || {}), JSON.stringify(job.platforms || [])]);

  function toggleAccount(accountId) {
    setAccountIds((current) =>
      current.includes(accountId)
        ? current.filter((item) => item !== accountId)
        : [...current, accountId],
    );
  }

  function togglePlatform(platform) {
    setPlatforms((current) =>
      current.includes(platform)
        ? current.filter((item) => item !== platform)
        : [...current, platform],
    );
  }

  async function saveTargets() {
    setSaving(true);
    try {
      await runVideoAction(job.id, 'update-targets', {
        accountIds,
        platforms,
      });
      await onSaved?.();
    } catch (error) {
      alert(error instanceof Error ? error.message : '타깃 저장 실패');
    } finally {
      setSaving(false);
    }
  }

  return (
    <div style={{ display: 'grid', gap: 8, marginTop: 8 }}>
      <div style={{ display: 'grid', gap: 6 }}>
        <span style={{ fontSize: 11, color: 'var(--fg-subtle)' }}>발행 계정</span>
        <div className="filters" style={{ marginBottom: 0 }}>
          {accounts.map((account) => (
            <button
              key={account.id}
              className={`filter-tab ${accountIds.includes(account.id) ? 'active' : ''}`}
              type="button"
              onClick={() => toggleAccount(account.id)}
            >
              {account.handle}
            </button>
          ))}
        </div>
      </div>

      <div style={{ display: 'grid', gap: 6 }}>
        <span style={{ fontSize: 11, color: 'var(--fg-subtle)' }}>플랫폼</span>
        <div className="filters" style={{ marginBottom: 0 }}>
          {['youtube', 'tiktok', 'instagram'].map((platform) => (
            <button
              key={platform}
              className={`filter-tab ${platforms.includes(platform) ? 'active' : ''}`}
              type="button"
              onClick={() => togglePlatform(platform)}
            >
              {platform}
            </button>
          ))}
        </div>
      </div>

      <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
        <button className="btn sm" type="button" onClick={saveTargets} disabled={saving}>
          {saving ? '저장 중...' : '타깃 저장'}
        </button>
      </div>
    </div>
  );
}

function VideosPage() {
  const accounts = window.SAJU.ACCOUNTS || [];
  const [jobs, setJobs] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const [generatingCaptions, setGeneratingCaptions] = React.useState(false);
  const [message, setMessage] = React.useState('');
  const [filter, setFilter] = React.useState('all');
  const [search, setSearch] = React.useState('');
  const [currentPage, setCurrentPage] = React.useState(1);
  const [draft, setDraft] = React.useState({
    title: '',
    caption: '',
    youtubeCaption: '',
    tiktokCaption: '',
    instagramCaption: '',
    thumbnailImageUrl: '',
    imageUrls: '',
    audioUrl: '',
    durationSeconds: '15',
    platforms: ['youtube', 'tiktok', 'instagram'],
    accountIds: [],
  });
  const [lastDraft, setLastDraft] = React.useState(null);
  const captionDebounceRef = React.useRef(null);

  // 캡션 입력 후 1.5초 대기 → 자동으로 AI 캡션 + 제목 생성
  React.useEffect(() => {
    if (!draft.caption.trim()) return;
    // 이미 플랫폼별 캡션이 수동으로 수정됐으면 자동 덮어쓰지 않음
    const alreadyFilled = draft.youtubeCaption.trim() || draft.tiktokCaption.trim() || draft.instagramCaption.trim();
    if (alreadyFilled) return;
    if (captionDebounceRef.current) clearTimeout(captionDebounceRef.current);
    captionDebounceRef.current = setTimeout(async () => {
      setGeneratingCaptions(true);
      setMessage('🤖 AI가 알고리즘 최적화 캡션 생성 중...');
      try {
        const response = await fetch('/api/admin/social-videos/generate-captions', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ title: draft.title.trim() || null, caption: draft.caption.trim() }),
        });
        const data = await response.json().catch(() => null);
        if (!response.ok || data?.error) throw new Error(data?.error || '캡션 생성 실패');
        setDraft((cur) => ({
          ...cur,
          title: cur.title.trim() ? cur.title : (data.title || cur.title),
          youtubeCaption: data.youtubeCaption || data.tiktokCaption || data.instagramCaption || cur.youtubeCaption,
          tiktokCaption: data.tiktokCaption || data.youtubeCaption || data.instagramCaption || cur.tiktokCaption,
          instagramCaption: data.instagramCaption || data.tiktokCaption || data.youtubeCaption || cur.instagramCaption,
        }));
        setMessage('✅ AI 캡션 자동 생성 완료 — 수정 후 영상 생성하세요.');
      } catch (e) {
        setMessage('캡션 자동 생성 실패: ' + (e.message || '알 수 없는 오류'));
      } finally {
        setGeneratingCaptions(false);
      }
    }, 1500);
    return () => clearTimeout(captionDebounceRef.current);
  }, [draft.caption]);

  async function load(options = {}) {
    const silent = Boolean(options.silent);
    if (!silent) setLoading(true);
    try {
      const nextJobs = await fetchVideoJobs();
      setJobs(nextJobs);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : '영상 목록을 불러오지 못했습니다.');
    } finally {
      if (!silent) setLoading(false);
    }
  }

  React.useEffect(() => {
    load();
  }, []);

  React.useEffect(() => {
    const hasRendering = jobs.some((job) => job.status === 'queued' || job.status === 'rendering');
    if (!hasRendering) return undefined;

    const timer = window.setInterval(() => {
      load({ silent: true });
    }, 5000);

    return () => window.clearInterval(timer);
  }, [jobs]);

  React.useEffect(() => {
    if (accounts.length === 0) return;
    setDraft((current) => (
      current.accountIds.length > 0
        ? current
        : { ...current, accountIds: accounts.map((account) => account.id) }
    ));
  }, [accounts.length]);

  const counts = {
    all: jobs.length,
    rendering: jobs.filter((job) => job.status === 'queued' || job.status === 'rendering').length,
    rendered: jobs.filter((job) => job.status === 'rendered').length,
    approved: jobs.filter((job) => job.status === 'approved').length,
    scheduled: jobs.filter((job) => job.status === 'scheduled').length,
    failed: jobs.filter((job) => job.status === 'failed').length,
  };

  const filtered = jobs.filter((job) => {
    if (filter !== 'all') {
      if (filter === 'rendering') {
        if (!(job.status === 'queued' || job.status === 'rendering')) return false;
      } else if (job.status !== filter) {
        return false;
      }
    }

    if (search) {
      const haystack = `${job.id} ${job.title || ''} ${job.caption || ''}`.toLowerCase();
      if (!haystack.includes(search.toLowerCase())) return false;
    }

    return true;
  });

  const pageSize = 3;
  const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
  const paginatedJobs = filtered.slice((currentPage - 1) * pageSize, currentPage * pageSize);

  React.useEffect(() => {
    setCurrentPage(1);
  }, [filter, search]);

  React.useEffect(() => {
    setCurrentPage((page) => Math.min(page, totalPages));
  }, [totalPages]);

  async function fillPlatformCaptions() {
    if (!draft.caption.trim()) {
      alert('본문 캡션을 먼저 입력해주세요.');
      return;
    }
    setGeneratingCaptions(true);
    setMessage('🤖 AI 알고리즘 최적화 캡션 생성 중...');
    try {
      const response = await fetch('/api/admin/social-videos/generate-captions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          title: draft.title.trim() || null,
          caption: draft.caption.trim(),
        }),
      });
      const data = await response.json().catch(() => null);
      if (!response.ok || data?.error) {
        throw new Error(data?.error || '캡션 생성 실패');
      }
      setDraft((current) => ({
        ...current,
        title: current.title.trim() ? current.title : (data.title || current.title),
        youtubeCaption: data?.youtubeCaption?.trim() || data?.tiktokCaption?.trim() || data?.instagramCaption?.trim() || current.youtubeCaption,
        tiktokCaption: data?.tiktokCaption?.trim() || data?.youtubeCaption?.trim() || data?.instagramCaption?.trim() || current.tiktokCaption,
        instagramCaption: data?.instagramCaption?.trim() || data?.tiktokCaption?.trim() || data?.youtubeCaption?.trim() || current.instagramCaption,
      }));
      setMessage('✅ 알고리즘 최적화 캡션 + 제목 생성 완료!');
    } catch (error) {
      const fallback = generatePlatformCaptions(draft.caption);
      setDraft((current) => ({ ...current, ...fallback }));
      setMessage('캡션 생성 실패: ' + (error instanceof Error ? error.message : '알 수 없는 오류'));
    } finally {
      setGeneratingCaptions(false);
    }
  }

  async function handleCreate() {
    const imageUrls = draft.imageUrls.split('\n').map((value) => value.trim()).filter(Boolean);
    if (imageUrls.length === 0) {
      alert('이미지 URL을 줄마다 하나씩 입력해 주세요.');
      return;
    }

    setSaving(true);
    setMessage('');
    try {
      await createVideoJob({
        title: draft.title.trim() || null,
        caption: draft.caption.trim() || null,
        youtubeCaption: englishOnlyText(draft.youtubeCaption) || englishOnlyText(draft.tiktokCaption) || englishOnlyText(draft.instagramCaption) || englishOnlyText(draft.caption) || null,
        tiktokCaption: englishOnlyText(draft.tiktokCaption) || englishOnlyText(draft.youtubeCaption) || englishOnlyText(draft.instagramCaption) || englishOnlyText(draft.caption) || null,
        instagramCaption: englishOnlyText(draft.instagramCaption) || englishOnlyText(draft.tiktokCaption) || englishOnlyText(draft.youtubeCaption) || englishOnlyText(draft.caption) || null,
        thumbnailImageUrl: draft.thumbnailImageUrl.trim() || null,
        imageUrls,
        audioUrl: draft.audioUrl.trim() || null,
        durationSeconds: Number(draft.durationSeconds) || 15,
        platforms: draft.platforms,
        accountIds: draft.accountIds,
      });

      setLastDraft(draft);
      setDraft((current) => ({
        ...current,
        title: '',
        caption: '',
        youtubeCaption: '',
        tiktokCaption: '',
        instagramCaption: '',
        thumbnailImageUrl: '',
        imageUrls: '',
        audioUrl: '',
        durationSeconds: '15',
        platforms: ['youtube', 'tiktok', 'instagram'],
        accountIds: accounts.map((account) => account.id),
      }));
      setMessage('영상 생성 요청이 접수되었습니다.');
      await load();
    } catch (error) {
      setMessage(error instanceof Error ? error.message : '영상 생성 요청 실패');
    } finally {
      setSaving(false);
    }
  }

  function togglePlatform(platform) {
    setDraft((current) => ({
      ...current,
      platforms: current.platforms.includes(platform)
        ? current.platforms.filter((item) => item !== platform)
        : [...current.platforms, platform],
    }));
  }

  function toggleDraftAccount(accountId) {
    setDraft((current) => ({
      ...current,
      accountIds: current.accountIds.includes(accountId)
        ? current.accountIds.filter((item) => item !== accountId)
        : [...current.accountIds, accountId],
    }));
  }

  async function act(jobId, action) {
    try {
      await runVideoAction(jobId, action);
      await load();
    } catch (error) {
      alert(error instanceof Error ? error.message : '영상 작업 처리 실패');
    }
  }

  return (
    <>
      <div className="page-header">
        <div>
          <h1 className="page-title">영상 작업</h1>
          <div className="page-subtitle">공통 캡션에서 플랫폼별 초안을 채우고, 썸네일까지 같이 관리하는 화면입니다.</div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn" onClick={load}><Icon name="refresh" size={13} />새로고침</button>
          {lastDraft && (
            <button className="btn" type="button" onClick={() => { setDraft(lastDraft); setLastDraft(null); }} title="이전 폼 내용 복원">
              ↩ 되돌리기
            </button>
          )}
          <button className="btn primary" onClick={handleCreate} disabled={saving}>
            <Icon name="plus" size={13} />
            {saving ? '생성중..' : '새 영상 요청'}
          </button>
        </div>
      </div>

      <div className="filters">
        {[
          ['all', '전체'],
          ['rendering', '렌더중'],
          ['rendered', '시안완료'],
          ['approved', '승인'],
          ['scheduled', '예약됨'],
          ['failed', '실패'],
        ].map(([key, label]) => (
          <button key={key} className={`filter-tab ${filter === key ? 'active' : ''}`} onClick={() => setFilter(key)}>
            <span>{label}</span>
            <span className="count">{counts[key]}</span>
          </button>
        ))}
        <div className="search" style={{ marginLeft: 'auto', minWidth: 220 }}>
          <Icon name="search" size={13} />
          <input placeholder="제목/ID 검색..." value={search} onChange={(event) => setSearch(event.target.value)} />
        </div>
      </div>

      <div className="split-3" style={{ marginBottom: 16 }}>
        <div className="card">
          <div className="card-header">
            <div>
              <div className="card-title">새 영상 작업</div>
              <div className="card-sub">공통 캡션만 먼저 쓰고, 버튼 한 번으로 플랫폼별 초안을 채울 수 있습니다.</div>
            </div>
          </div>
          <div className="card-body" style={{ display: 'grid', gap: 12 }}>
            <label className="field">
              <span className="field-label">작업 제목</span>
              <input className="input" value={draft.title} onChange={(event) => setDraft((current) => ({ ...current, title: event.target.value }))} placeholder="예: 5월 직장운 캡처 영상" />
            </label>
            <label className="field">
              <span className="field-label">공통 캡션</span>
              <textarea className="textarea" value={draft.caption} onChange={(event) => setDraft((current) => ({ ...current, caption: event.target.value }))} placeholder="모든 플랫폼의 기본 캡션" />
            </label>
            <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
              <button className="btn sm" type="button" onClick={fillPlatformCaptions} disabled={generatingCaptions}>
                {generatingCaptions ? 'Generating...' : '플랫폼별 초안 채우기'}
              </button>
            </div>
            <div className="split-3">
              <label className="field">
                <span className="field-label">YouTube 캡션</span>
                <textarea className="textarea" value={draft.youtubeCaption} onChange={(event) => setDraft((current) => ({ ...current, youtubeCaption: event.target.value }))} placeholder="비워두면 공통 캡션 사용" />
              </label>
              <label className="field">
                <span className="field-label">TikTok 캡션</span>
                <textarea className="textarea" value={draft.tiktokCaption} onChange={(event) => setDraft((current) => ({ ...current, tiktokCaption: event.target.value }))} placeholder="비워두면 공통 캡션 사용" />
              </label>
              <label className="field">
                <span className="field-label">Instagram 캡션</span>
                <textarea className="textarea" value={draft.instagramCaption} onChange={(event) => setDraft((current) => ({ ...current, instagramCaption: event.target.value }))} placeholder="비워두면 공통 캡션 사용" />
              </label>
            </div>
            <label className="field">
              <span className="field-label">썸네일 이미지 URL</span>
              <input className="input" value={draft.thumbnailImageUrl} onChange={(event) => setDraft((current) => ({ ...current, thumbnailImageUrl: event.target.value }))} placeholder="https://...cover.jpg" />
            </label>
            <label className="field">
              <span className="field-label">이미지 URL 목록</span>
              <textarea className="textarea" value={draft.imageUrls} onChange={(event) => setDraft((current) => ({ ...current, imageUrls: event.target.value }))} placeholder={'https://...capture-01.jpg\nhttps://...capture-02.jpg'} />
            </label>
            <div className="split-2">
              <label className="field">
                <span className="field-label">배경음악 URL</span>
                <input className="input" value={draft.audioUrl} onChange={(event) => setDraft((current) => ({ ...current, audioUrl: event.target.value }))} placeholder="https://...bgm.mp3" />
              </label>
              <label className="field">
                <span className="field-label">길이(초)</span>
                <input className="input" value={draft.durationSeconds} onChange={(event) => setDraft((current) => ({ ...current, durationSeconds: event.target.value }))} />
              </label>
            </div>
            <div className="field">
              <span className="field-label">발행 계정</span>
              <div className="filters" style={{ marginBottom: 0 }}>
                {accounts.map((account) => (
                  <button key={account.id} className={`filter-tab ${draft.accountIds.includes(account.id) ? 'active' : ''}`} type="button" onClick={() => toggleDraftAccount(account.id)}>
                    {account.handle}
                  </button>
                ))}
              </div>
            </div>
            <div className="field">
              <span className="field-label">플랫폼</span>
              <div className="filters" style={{ marginBottom: 0 }}>
                {['youtube', 'tiktok', 'instagram'].map((platform) => (
                  <button key={platform} className={`filter-tab ${draft.platforms.includes(platform) ? 'active' : ''}`} type="button" onClick={() => togglePlatform(platform)}>
                    {platform}
                  </button>
                ))}
              </div>
            </div>
            {message ? <div className="helper">{message}</div> : null}
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', paddingTop: 4 }}>
              {lastDraft && (
                <button className="btn" type="button" onClick={() => { setDraft(lastDraft); setLastDraft(null); }}>
                  ↩ 되돌리기
                </button>
              )}
              <button className="btn primary" type="button" onClick={handleCreate} disabled={saving}>
                <Icon name="plus" size={13} />
                {saving ? '생성중..' : '새 영상 요청'}
              </button>
            </div>
          </div>
        </div>

        <div className="card">
          <div className="card-header">
            <div>
              <div className="card-title">작업 흐름</div>
              <div className="card-sub">초안 자동 채우기, 진행률 확인, 승인 전 검수까지 이어지는 흐름입니다.</div>
            </div>
          </div>
          <div className="card-body" style={{ display: 'grid', gap: 10 }}>
            <div className="chip"><VideoBadge status="queued" /> 요청 접수</div>
            <div className="chip"><VideoBadge status="rendering" /> VPS 렌더링 + 진행률</div>
            <div className="chip"><VideoBadge status="rendered" /> 시안 확인 + 캡션 검수</div>
            <div className="chip"><VideoBadge status="approved" /> 승인 후 발행 가능</div>
            <div className="chip"><VideoBadge status="failed" /> 오류 확인 필요</div>
          </div>
        </div>
      </div>

      <div className="card" style={{ overflow: 'hidden' }}>
        <table className="table">
          <thead>
            <tr>
              <th style={{ width: 90 }}>ID</th>
              <th>작업</th>
              <th style={{ width: 150 }}>플랫폼</th>
              <th style={{ width: 100 }}>길이</th>
              <th style={{ width: 120 }}>상태</th>
              <th style={{ width: 150 }}>예약/완료</th>
              <th style={{ width: 260 }}></th>
            </tr>
          </thead>
          <tbody>
            {loading ? (
              <tr><td colSpan="7"><div className="empty">불러오는 중..</div></td></tr>
            ) : filtered.length === 0 ? (
              <tr><td colSpan="7"><div className="empty">영상 작업이 없습니다.</div></td></tr>
            ) : (
              paginatedJobs.map((job) => (
                <tr key={job.id}>
                  <td className="mono" style={{ color: 'var(--fg-subtle)', fontSize: 11 }}>{job.id.slice(0, 8)}</td>
                  <td>
                    <div style={{ display: 'grid', gap: 6 }}>
                      <strong style={{ fontSize: 12.5 }}>{job.title || '제목 없는 영상 작업'}</strong>
                      <span style={{ color: 'var(--fg-subtle)', fontSize: 11.5 }}>
                        이미지 {Array.isArray(job.image_urls) ? job.image_urls.length : 0}장 · 음악 {job.audio_url ? '있음' : '없음'}
                      </span>
                      {(job.status === 'queued' || job.status === 'rendering' || job.status === 'rendered') ? (
                        <ProgressBar job={job} />
                      ) : null}
                      <span style={{ color: 'var(--fg-subtle)', fontSize: 11.5 }}>
                        계정 {getSelectedAccountIds(job).length}개 선택됨
                      </span>
                      {getThumbnailUrl(job) ? (
                        <a href={getThumbnailUrl(job)} target="_blank" rel="noreferrer" style={{ fontSize: 11.5, color: 'var(--fg)' }}>
                          썸네일 이미지 보기
                        </a>
                      ) : null}
                      {job.video_url ? (
                        <a href={job.video_url} target="_blank" rel="noreferrer" style={{ fontSize: 11.5, color: 'var(--fg)' }}>
                          생성된 영상 보기
                        </a>
                      ) : null}
                      {job.caption ? (
                        <div style={{ fontSize: 11.5, color: 'var(--fg-subtle)', whiteSpace: 'pre-wrap' }}>
                          공통 캡션: {job.caption}
                        </div>
                      ) : null}
                      <div style={{ display: 'grid', gap: 4 }}>
                        {['youtube', 'tiktok', 'instagram'].map((platform) => {
                          const platformCaption = getPlatformCaption(job, platform);
                          return platformCaption ? (
                            <div key={platform} style={{ fontSize: 11, color: 'var(--fg-subtle)', whiteSpace: 'pre-wrap' }}>
                              {platform}: {platformCaption}
                            </div>
                          ) : null;
                        })}
                      </div>
                      {job.last_error ? (
                        <span style={{ fontSize: 11.5, color: 'var(--status-failed-fg)' }}>{job.last_error}</span>
                      ) : null}
                      <VideoTargetsEditor job={job} onSaved={load} />
                    </div>
                  </td>
                  <td>
                    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                      {(job.platforms || []).map((platform) => <span key={platform} className="chip">{platform}</span>)}
                    </div>
                  </td>
                  <td className="mono" style={{ color: 'var(--fg-muted)', fontSize: 11.5 }}>{job.duration_seconds || 15}s</td>
                  <td><VideoBadge status={job.status} /></td>
                  <td className="mono" style={{ color: 'var(--fg-muted)', fontSize: 11.5 }}>
                    {job.scheduled_for ? fmtTime(job.scheduled_for) : job.published_at ? fmtTime(job.published_at) : '-'}
                  </td>
                  <td>
                    <div style={{ display: 'flex', gap: 4, justifyContent: 'flex-end', flexWrap: 'wrap' }}>
                      {job.status === 'rendered' && (
                        <>
                          <button className="btn sm" onClick={() => act(job.id, 'schedule')}>예약</button>
                          <button className="btn sm primary" onClick={() => act(job.id, 'approve')}>승인</button>
                        </>
                      )}
                      {(job.status === 'approved' || job.status === 'scheduled') && (
                        <button className="btn sm primary" onClick={() => act(job.id, 'publish')}>즉시발행</button>
                      )}
                      {(job.status === 'queued' || job.status === 'rendering') && (
                        <button className="btn sm" onClick={load}><Icon name="refresh" size={11} />확인</button>
                      )}
                      {job.status !== 'publishing' && job.status !== 'published' && (
                        <button className="btn sm" onClick={() => act(job.id, 'delete')}>삭제</button>
                      )}
                      {job.video_url && <button className="btn ghost sm" onClick={() => window.open(job.video_url, '_blank')}>보기</button>}
                    </div>
                  </td>
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>
      {filtered.length > 0 ? (
        <div style={{ display: 'flex', justifyContent: 'center', gap: 8, marginTop: 16, flexWrap: 'wrap' }}>
          {Array.from({ length: totalPages }, (_, index) => {
            const page = index + 1;
            return (
              <button
                key={page}
                className={`filter-tab ${currentPage === page ? 'active' : ''}`}
                type="button"
                onClick={() => setCurrentPage(page)}
              >
                {page}
              </button>
            );
          })}
        </div>
      ) : null}
    </>
  );
}

window.VideosPage = VideosPage;
