import { useEffect, useRef, useState } from 'react'
import type { CSSProperties, ChangeEvent } from 'react'
import { SCEN, SCEN_KEYS, scGrad, fmt } from './data'
import type { FeedItem, Scen, Tab } from './data'
import {
  createJob, getJob, listComments, addComment,
  searchPlace, listSocial, socialConfig, connectSocial, disconnectSocial, uploadToSocial,
  getProducts, purchaseProduct, updateProfile,
} from './api'
import type { CommentNode, PlaceItem, SocialAccount, Platform, Product, Me, ProfileBody } from './api'

/** 파일 → 256px 정사각 JPEG data URL (서버 스토리지 없이 프로필 이미지 저장) */
function fileToAvatar(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    const img = new Image()
    const url = URL.createObjectURL(file)
    img.onload = () => {
      const size = 256
      const c = document.createElement('canvas')
      c.width = size; c.height = size
      const ctx = c.getContext('2d')!
      const s = Math.min(img.width, img.height)
      ctx.drawImage(img, (img.width - s) / 2, (img.height - s) / 2, s, s, 0, 0, size, size)
      URL.revokeObjectURL(url)
      resolve(c.toDataURL('image/jpeg', 0.85))
    }
    img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('image load fail')) }
    img.src = url
  })
}

/** 프로필 아바타 — 이미지 있으면 사진, 없으면 이니셜 */
export function Avatar({ url, name, className = '' }: { url: string | null; name: string; className?: string }) {
  return (
    <span className={'avatar' + (className ? ' ' + className : '')}>
      {url ? <img src={url} alt="" /> : (name.slice(0, 1) || 'ㅅ')}
    </span>
  )
}
import {
  IcHome, IcSearch, IcUser, IcPlus, IcPlay, IcHeart, IcHeartLine,
  IcComment, IcShare, IcLink, IcGrid, IcBack,
  IcYoutube, IcInstagram, IcTiktok, IcUpload,
} from './icons'

const sc = (v: string): CSSProperties => ({ ['--sc']: v } as CSSProperties)

/* SNS 플랫폼 메타 (표시용) — 유튜브만 지원 */
export const PLATFORMS: { key: Platform; name: string; Icon: () => JSX.Element; hex: string }[] = [
  { key: 'youtube', name: '유튜브', Icon: IcYoutube, hex: '#ff0033' },
]

/* ============================ 로그인 모달 (카카오 전용) ============================ */
export function Login({ open, onClose, onKakao, busy }: {
  open: boolean; onClose: () => void; onKakao: () => void; busy: boolean
}) {
  if (!open) return null
  return (
    <div className="login" onClick={onClose}>
      <div className="login-card" onClick={(e) => e.stopPropagation()}>
        <button className="login-x" aria-label="닫기" onClick={onClose}>✕</button>
        <div className="login-brand"><b>썰박스</b><span>SSULBOX</span></div>
        <p className="login-tag">로그인하면 썰박스를 만들고<br />좋아요·댓글을 남길 수 있어요</p>
        <div className="login-actions">
          <button className="kakao" disabled={busy} onClick={onKakao}>
            <span className="ki"></span>{busy ? '잠시만요…' : '카카오톡으로 로그인'}
          </button>
        </div>
      </div>
    </div>
  )
}

/* ============================ 좌측 사이드바 / 하단 탭바 ============================ */
export function Nav({ tab, onTab, nickname, avatar, onLogin, onLogout }: {
  tab: Tab; onTab: (t: Tab | 'create') => void
  nickname: string | null; avatar: string | null; onLogin: () => void; onLogout: () => void
}) {
  return (
    <nav className="nav">
      <div className="brand"><b>썰박스</b><span>SSULBOX</span></div>
      <button data-tab="home" aria-current={tab === 'home'} onClick={() => onTab('home')}>
        <span className="i"><IcHome /></span><span className="lbl">홈</span>
      </button>
      <button data-tab="search" aria-current={tab === 'search'} onClick={() => onTab('search')}>
        <span className="i"><IcSearch /></span><span className="lbl">검색</span>
      </button>
      <button className="create" onClick={() => onTab('create')}>
        <span className="plus"><IcPlus /></span><span className="lbl">만들기</span>
      </button>
      <button data-tab="channel" aria-current={tab === 'channel'} onClick={() => onTab('channel')}>
        <span className="i"><IcUser /></span><span className="lbl">내 채널</span>
      </button>
      <div className="nav-foot">
        {nickname ? (
          <div className="nav-me">
            <span className="nav-av">{avatar ? <img src={avatar} alt="" /> : nickname.slice(0, 1)}</span>
            <span className="nav-nm">{nickname}</span>
            <button className="nav-logout" onClick={onLogout}>로그아웃</button>
          </div>
        ) : (
          <button className="nav-login" onClick={onLogin}>로그인</button>
        )}
      </div>
    </nav>
  )
}

export function TopBar({ tab, nickname, onLogin }: {
  tab: Tab; nickname: string | null; onLogin: () => void
}) {
  if (tab === 'home')
    return (
      <header className="topbar">
        <div className="wordmark"><b>썰박스</b><span className="dot">SSULBOX</span></div>
        <div className="actions">
          {nickname
            ? <button aria-label="알림"><IcHeartLine /></button>
            : <button className="tb-login" onClick={onLogin}>로그인</button>}
        </div>
      </header>
    )
  if (tab === 'search') return <header className="topbar"><h2>검색</h2></header>
  return (
    <header className="topbar">
      <h2>@{nickname ?? '게스트'}</h2>
      <div className="actions">
        {nickname
          ? <button aria-label="설정">☰</button>
          : <button className="tb-login" onClick={onLogin}>로그인</button>}
      </div>
    </header>
  )
}

/* ============================ 카드 / 피드 ============================ */
export function Card({ it, onOpen }: { it: FeedItem; onOpen: (id: string) => void }) {
  const s = SCEN[it.sc]
  return (
    <button className="card" style={{ background: scGrad(it.sc) }} onClick={() => onOpen(it.id)}>
      <div className="cover">
        {it.video
          ? <video className="cov-vid" src={it.video + '#t=0.1'} muted playsInline preload="metadata" />
          : <span className="emoji">{s.emoji}</span>}
      </div>
      <span className="badge"><i style={{ background: s.hex }} />{s.name}</span>
      <span className="play-fab"><IcPlay /></span>
      <div className="shade" />
      <div className="meta">
        <div className="title">{it.title}</div>
        <div className="sub"><IcPlay />{fmt(it.views)} · ♥ {fmt(it.likes)}</div>
      </div>
    </button>
  )
}

export function Feed({ items, onOpen }: { items: FeedItem[]; onOpen: (id: string) => void }) {
  if (!items.length)
    return (
      <div className="empty">
        아직 만든 썰박스가 없어요.<br />하단 <b>＋ 만들기</b>로 첫 썰박스를 만들어보세요.
      </div>
    )
  return <div className="grid">{items.map((it) => <Card key={it.id} it={it} onOpen={onOpen} />)}</div>
}

/* ============================ 화면: 홈 / 검색 / 채널 ============================ */
export function Home({ data, onOpen }: { data: FeedItem[]; onOpen: (id: string) => void }) {
  return (
    <>
      <div className="sec-h"><h3>둘러보기</h3></div>
      <Feed items={data} onOpen={onOpen} />
    </>
  )
}

export function Search({ data, onOpen }: { data: FeedItem[]; onOpen: (id: string) => void }) {
  const [query, setQuery] = useState('')
  const [filter, setFilter] = useState<'all' | Scen>('all')
  const chips: [('all' | Scen), string][] = [['all', '전체'], ['joseon', '조선'], ['samgukji', '삼국지'], ['greek', '그리스']]
  let items = data.filter((it) => filter === 'all' || it.sc === filter)
  if (query.trim()) items = items.filter((it) => it.title.includes(query.trim()))
  return (
    <>
      <div className="searchbar">
        <div className="searchbox">
          <IcSearch />
          <input placeholder="가게·인물·키워드 검색" value={query} onChange={(e) => setQuery(e.target.value)} />
        </div>
      </div>
      <div className="chips">
        {chips.map(([k, l]) => (
          <button key={k} className="chip" aria-pressed={filter === k} onClick={() => setFilter(k)}>{l}</button>
        ))}
      </div>
      <Feed items={items} onOpen={onOpen} />
    </>
  )
}

/* ============================ 프로필 편집 시트 ============================ */
export function ProfileEdit({ open, onClose, me, onSaved, toast }: {
  open: boolean; onClose: () => void; me: Me | null
  onSaved: (me: Me) => void; toast: (m: string) => void
}) {
  const [nickname, setNickname] = useState('')
  const [bio, setBio] = useState('')
  const [avatar, setAvatar] = useState<string | null>(null)
  const [busy, setBusy] = useState(false)
  const fileRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    if (open && me) { setNickname(me.nickname); setBio(me.bio ?? ''); setAvatar(me.profile_image_url) }
  }, [open, me])

  async function pick(e: ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0]
    e.target.value = ''
    if (!f) return
    try { setAvatar(await fileToAvatar(f)) } catch { toast('이미지를 불러오지 못했어요.') }
  }

  async function save() {
    if (!me) return
    setBusy(true)
    const body: ProfileBody = { nickname, bio }
    if (avatar !== me.profile_image_url) body.profile_image_url = avatar ?? ''
    const r = await updateProfile(body)
    toast(r.message)
    if (r.ok && r.me) { onSaved(r.me); onClose() }
    setBusy(false)
  }

  return (
    <section className={'sheet' + (open ? ' on' : '')} aria-hidden={!open}>
      <div className="grab" />
      <div className="sheet-h"><h3>프로필 편집</h3><button aria-label="닫기" onClick={onClose}>✕</button></div>
      <div className="sheet-body">
        <div className="pe-avatar">
          <Avatar url={avatar} name={nickname} className="lg" />
          <div className="pe-av-btns">
            <button onClick={() => fileRef.current?.click()}>사진 변경</button>
            {avatar && <button className="rm" onClick={() => setAvatar(null)}>제거</button>}
          </div>
          <input ref={fileRef} type="file" accept="image/*" hidden onChange={pick} />
        </div>
        <div className="field">
          <label>닉네임 <span className="lbl-sub">(채널 이름)</span></label>
          <div className="input"><input value={nickname} maxLength={20}
            onChange={(e) => setNickname(e.target.value)} placeholder="채널 이름" /></div>
        </div>
        <div className="field">
          <label>한 줄 소개</label>
          <textarea className="pe-bio" value={bio} maxLength={200} rows={3}
            onChange={(e) => setBio(e.target.value)} placeholder="내 채널을 소개해보세요 (예: 성수동 골목 냉면집 사장 🍜)" />
          <div className="hint" style={{ textAlign: 'right' }}>{bio.length}/200</div>
        </div>
      </div>
      <div className="sheet-foot">
        <button className="cta" disabled={busy || !nickname.trim()} onClick={save}>{busy ? '저장 중…' : '저장'}</button>
      </div>
    </section>
  )
}

/* ============================ 크레딧 충전 시트 ============================ */
export function ChargeSheet({ open, onClose, onDone, toast }: {
  open: boolean; onClose: () => void; onDone: (credits: number) => void; toast: (m: string) => void
}) {
  const [products, setProducts] = useState<Product[]>([])
  const [busy, setBusy] = useState<string | null>(null)

  useEffect(() => { if (open) getProducts().then(setProducts) }, [open])

  async function buy(p: Product) {
    setBusy(p.id)
    try {
      const r = await purchaseProduct(p.id)
      toast(r.message)
      if (r.ok) { if (typeof r.credits === 'number') onDone(r.credits); onClose() }
    } finally { setBusy(null) }
  }

  return (
    <section className={'sheet' + (open ? ' on' : '')} aria-hidden={!open}>
      <div className="grab" />
      <div className="sheet-h"><h3>크레딧 충전</h3><button aria-label="닫기" onClick={onClose}>✕</button></div>
      <div className="sheet-body">
        <div className="step-label">영상 <b>1편 = 1크레딧</b> · 필요한 만큼 충전하세요</div>
        <div className="prods">
          {products.map((p) => (
            <button key={p.id} className={'prod' + (p.best ? ' best' : '')} disabled={busy === p.id} onClick={() => buy(p)}>
              {p.best && <span className="prod-tag">인기</span>}
              <span className="prod-cr"><b>{p.credits + p.bonus}</b>크레딧</span>
              {p.bonus > 0 && <span className="prod-bonus">기본 {p.credits} + 보너스 {p.bonus}</span>}
              <span className="prod-name">{p.label}</span>
              <span className="prod-price">{busy === p.id ? '처리 중…' : '₩' + p.price.toLocaleString()}</span>
            </button>
          ))}
        </div>
        <div className="hint">결제(PG) 연동 전이라 지금은 즉시 지급됩니다.</div>
      </div>
    </section>
  )
}

/* ============================ SNS 연동 패널 ============================ */
export function SnsPanel({ toast }: { toast: (m: string) => void }) {
  const [accounts, setAccounts] = useState<SocialAccount[]>([])
  const [enabled, setEnabled] = useState<Partial<Record<Platform, boolean>>>({})
  const [busy, setBusy] = useState<Platform | null>(null)
  const connected = new Set(accounts.map((a) => a.platform))

  const refresh = () => listSocial().then(setAccounts)
  useEffect(() => { refresh(); socialConfig().then(setEnabled) }, [])

  async function toggle(p: Platform) {
    setBusy(p)
    try {
      if (connected.has(p)) {
        if (await disconnectSocial(p)) { toast('연동을 해제했어요.'); await refresh() }
        else toast('해제에 실패했어요.')
      } else {
        const r = await connectSocial(p)
        if (r.authUrl) { window.location.href = r.authUrl; return }   // Google 동의 페이지로 이동
        toast(r.message)
      }
    } finally { setBusy(null) }
  }

  return (
    <div className="sns-card">
      <div className="sns-h"><span>SNS 연동</span><em>완성한 썰박스를 바로 업로드</em></div>
      <div className="sns-list">
        {PLATFORMS.map(({ key, name, Icon, hex }) => {
          const on = connected.has(key)
          const ready = enabled[key] ?? false
          return (
            <div className="sns-row" key={key}>
              <span className="sns-ic" style={sc(hex)}><Icon /></span>
              <span className="sns-name">{name}{on && <i className="sns-dot" />}
                {!ready && !on && <em className="sns-soon">곧 지원</em>}
              </span>
              <button className={'sns-btn' + (on ? ' on' : '')} disabled={busy === key} onClick={() => toggle(key)}>
                {busy === key ? '…' : on ? '연동됨' : '연동'}
              </button>
            </div>
          )
        })}
      </div>
    </div>
  )
}

export function Channel({ data, credits, nickname, avatar, bio, onOpen, toast, onLogout, onCharge, onEditProfile }: {
  data: FeedItem[]; credits: number; nickname: string; avatar: string | null; bio: string | null
  onOpen: (id: string) => void; toast: (m: string) => void; onLogout: () => void
  onCharge: () => void; onEditProfile: () => void
}) {
  const perVideo = 1
  const canMake = Math.floor(credits / perVideo)
  return (
    <div className="ch-wrap">
      <div className="profile">
        <div className="row">
          <Avatar url={avatar} name={nickname} />
          <div className="stats">
            <div><b>{data.length}</b><span>만든 영상</span></div>
            <div><b>{fmt(credits)}</b><span>남은 크레딧</span></div>
            <div><b>{data.length}</b><span>이번 달</span></div>
          </div>
        </div>
        <div className="bio">
          <div className="name">{nickname}</div>
          {bio ? <div className="bio-txt">{bio}</div> : <div className="bio-txt muted">소개를 추가해보세요</div>}
        </div>
        <div className="token-card">
          <div className="tk-left">
            <span>남은 크레딧</span>
            <b>{fmt(credits)}</b>
            <em>약 {canMake}편 더 생성 가능 (편당 {perVideo}크레딧)</em>
          </div>
          <button className="tk-btn" onClick={onCharge}>충전</button>
        </div>
        <div className="pf-actions"><button onClick={onEditProfile}>프로필 편집</button><button onClick={onLogout}>로그아웃</button></div>
        <SnsPanel toast={toast} />
        <div className="pf-tabs"><button aria-selected="true"><IcGrid /></button></div>
      </div>
      <Feed items={data} onOpen={onOpen} />
    </div>
  )
}

/* ============================ 만들기 시트 ============================ */
type Phase = 'pick' | 'form' | 'making'

export function CreateSheet({ open, onClose, onCreated }: {
  open: boolean
  onClose: () => void
  onCreated: () => Promise<void> | void
}) {
  const [phase, setPhase] = useState<Phase>('pick')
  const [scn, setScn] = useState<Scen | null>(null)
  const [input, setInput] = useState('')
  const [status, setStatus] = useState('')
  const [log, setLog] = useState<string[]>([])
  const [err, setErr] = useState('')
  const [results, setResults] = useState<PlaceItem[]>([])
  const [selected, setSelected] = useState<PlaceItem | null>(null)
  const [searching, setSearching] = useState(false)
  const [searched, setSearched] = useState(false)
  const timer = useRef<number | undefined>(undefined)

  useEffect(() => {
    if (open) {
      setPhase('pick'); setScn(null); setInput('')
      setStatus(''); setLog([]); setErr('')
      setResults([]); setSelected(null); setSearching(false); setSearched(false)
    }
    return () => { if (timer.current) window.clearTimeout(timer.current) }
  }, [open])

  const isUrl = (s: string) => /naver\.me|map\.naver|place\.naver|^https?:\/\//i.test(s.trim())

  async function runSearch() {
    const q = input.trim()
    if (q.length < 2 || isUrl(q)) return
    setSearching(true); setSearched(false); setResults([]); setSelected(null)
    try {
      const items = await searchPlace(q)
      setResults(items); setSearched(true)
    } catch { setResults([]); setSearched(true) } finally { setSearching(false) }
  }

  // 링크(place_url)가 확정됐거나(=선택) URL을 직접 붙여넣으면 생성 가능
  const canSubmit = !!selected || isUrl(input)

  const s = scn ? SCEN[scn] : null

  async function poll(id: string) {
    try {
      const j = await getJob(id)
      setLog(j.log || [])
      if (j.status === 'done') { setStatus('완성!'); await onCreated(); onClose(); return }
      if (j.status === 'error') { setErr(j.error || '생성 중 오류가 발생했어요.'); return }
      const names = ['대본 생성', '스토리보드', '이미지·음성', '영상 합성']
      setStatus(j.status === 'queued' ? '대기 중…' : `${names[Math.max(0, (j.step || 1) - 1)]}… (${j.step || 1}/4)`)
    } catch {
      setStatus('상태 확인 중…')
    }
    timer.current = window.setTimeout(() => poll(id), 3000)
  }

  async function submit() {
    if (!scn) return
    // 선택된 가게가 있으면 그 place_url 로 정확히 크롤링. 아니면 붙여넣은 URL 그대로.
    const payload = selected ? selected.place_url : input.trim()
    setPhase('making'); setErr(''); setStatus('생성 준비 중…')
    try {
      const res = await createJob({ scenario: scn, input: payload })
      poll(res.id)
    } catch (e) {
      setErr(e instanceof Error ? e.message : '생성 요청에 실패했어요.')
    }
  }

  return (
    <section className={'sheet' + (open ? ' on' : '')} aria-hidden={!open}>
      <div className="grab" />
      <div className="sheet-h">
        <h3>썰박스 만들기</h3>
        <button aria-label="닫기" onClick={onClose}>✕</button>
      </div>

      <div className="sheet-body">
        {phase === 'pick' && (
          <>
            <div className="step-label"><b>1단계</b> · 어떤 이야기로 만들까요?</div>
            <div className="scn-list">
              {SCEN_KEYS.map((k) => {
                const si = SCEN[k]
                return (
                  <button key={k} className="scn" aria-pressed={scn === k} style={sc(si.hex)} onClick={() => setScn(k)}>
                    <span className="ic" style={sc(si.hex)}>{si.emoji}</span>
                    <span className="tx"><span className="t">{si.name}</span><span className="d">{si.desc}</span></span>
                    <span className="ck" />
                  </button>
                )
              })}
            </div>
          </>
        )}

        {phase === 'form' && s && (
          <>
            <div className="step-label"><b>2단계</b> · <span style={{ color: s.hex }}>{s.emoji} {s.name}</span> 로 만들 가게 선택</div>
            <div className="field">
              <label>업장명으로 검색 <span className="lbl-sub">(같은 이름이 여러 개면 주소로 골라요)</span></label>
              <div className="input search">
                <IcSearch />
                <input
                  placeholder="가게 이름 (예: 골목냉면)  ·  또는 네이버 링크 붙여넣기" value={input} autoFocus
                  onChange={(e) => { setInput(e.target.value); if (selected) { setSelected(null) } }}
                  onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); runSearch() } }}
                />
                <button type="button" className="srch-btn" disabled={searching || input.trim().length < 2 || isUrl(input)}
                  onClick={runSearch}>{searching ? '검색 중' : '검색'}</button>
              </div>

              {selected ? (
                <div className="picked">
                  <div className="pk-body">
                    <b>{selected.title}</b>
                    <span>{selected.category ? selected.category + ' · ' : ''}{selected.address || selected.roadAddress}</span>
                  </div>
                  <button type="button" className="pk-change" onClick={() => { setSelected(null); runSearch() }}>변경</button>
                </div>
              ) : isUrl(input) ? (
                <div className="hint ok">✓ 네이버 링크로 이 가게를 직접 크롤링합니다.</div>
              ) : searching ? (
                <div className="place-loading"><div className="spin sm" />가게를 찾는 중… (몇 초 걸려요)</div>
              ) : results.length > 0 ? (
                <ul className="places">
                  {results.map((p, i) => (
                    <li key={i}>
                      <button type="button" onClick={() => setSelected(p)}>
                        <b>{p.title}</b>
                        <span>{p.category ? p.category + ' · ' : ''}{p.address || p.roadAddress}</span>
                      </button>
                    </li>
                  ))}
                </ul>
              ) : searched ? (
                <div className="hint warn">검색 결과가 없어요. 이름을 바꾸거나 네이버 플레이스 링크를 붙여넣어 주세요.</div>
              ) : (
                <div className="hint">가게를 선택하면 AI가 그 가게의 공식 사진·브리핑을 크롤링해 썰박스에 씁니다.</div>
              )}
            </div>
          </>
        )}

        {phase === 'making' && s && (
          <div className="making" style={sc(s.hex)}>
            {!err ? (
              <>
                <div className="spin" />
                <h4>{s.emoji} {s.name} 썰박스 생성 중…</h4>
                <p>{status}<br />AI가 대본→그림→목소리→합성 중이에요. 보통 2~4분 걸려요.</p>
                <div className="job">
                  <div>· 시나리오 &nbsp;<b>{s.name}</b></div>
                  <div>· 가게 &nbsp;<b>{selected ? selected.title : input}</b></div>
                  {selected && <div>· 위치 &nbsp;<b>{selected.address || selected.roadAddress}</b></div>}
                  {log.length > 0 && <div style={{ marginTop: 8, whiteSpace: 'pre-wrap' }}>{log.slice(-4).join('\n')}</div>}
                </div>
              </>
            ) : (
              <>
                <h4>⚠️ 생성 실패</h4>
                <p>{err}</p>
              </>
            )}
          </div>
        )}
      </div>

      <div className="sheet-foot">
        {phase === 'pick' && <button className="cta" disabled={!scn} onClick={() => setPhase('form')}>다음</button>}
        {phase === 'form' && <button className="cta" disabled={!canSubmit} onClick={submit}>썰박스 만들기</button>}
        {phase === 'making' && <button className="cta" disabled={!err} onClick={onClose}>{err ? '닫기' : '생성 중…'}</button>}
      </div>
    </section>
  )
}

/* ============================ 릴스형 뷰어 (좋아요·댓글 연동) ============================ */
export function Viewer({ item, onClose, onLike, toast }: {
  item: FeedItem | null; onClose: () => void; onLike: (id: string) => void; toast: (m: string) => void
}) {
  const s = item ? SCEN[item.sc] : null
  const [comments, setComments] = useState<CommentNode[]>([])
  const [open, setOpen] = useState(false)
  const [text, setText] = useState('')
  const [shareOpen, setShareOpen] = useState(false)
  const [accounts, setAccounts] = useState<SocialAccount[]>([])
  const [upBusy, setUpBusy] = useState<Platform | null>(null)

  useEffect(() => {
    setOpen(false); setText(''); setShareOpen(false)
    if (item) listComments(item.id).then(setComments)
    else setComments([])
  }, [item?.id])

  function openShare() {
    setShareOpen(true)
    listSocial().then(setAccounts)
  }
  async function upload(p: Platform) {
    if (!item) return
    setUpBusy(p)
    try {
      const r = await uploadToSocial(p, item.id)
      toast(r.pending ? '🔑 곧 지원돼요 — 플랫폼 키 설정 후 자동 활성화됩니다.' : r.message)
      if (r.ok) setShareOpen(false)
    } finally { setUpBusy(null) }
  }

  async function send() {
    if (!item || !text.trim()) return
    await addComment(item.id, text.trim())
    setText('')
    setComments(await listComments(item.id))
  }

  const commentCount = comments.reduce((n, c) => n + 1 + (c.replies?.length ?? 0), 0)

  return (
    <div className={'viewer' + (item ? ' on' : '')} onClick={onClose}>
      {item && s && (
        <>
          <div className="vframe" onClick={(e) => e.stopPropagation()}>
            <div className="vtop">
              <button aria-label="뒤로가기" onClick={onClose}><IcBack /></button>
              <h2>썰박스</h2>
            </div>
            <div className="vstage" style={{ background: scGrad(item.sc) }}>
              {item.video ? (
                <video src={item.video} playsInline controls autoPlay />
              ) : (
                <>
                  <div className="cover"><span className="emoji">{s.emoji}</span></div>
                  <button className="vbig" aria-label="재생"><IcPlay /></button>
                </>
              )}
            </div>
            <div className="vrail">
              <button className={item.liked ? 'liked' : ''} onClick={() => onLike(item.id)}>
                <IcHeart /><span>{fmt(item.likes)}</span>
              </button>
              <button onClick={() => setOpen(true)}><IcComment /><span>{fmt(item.comments ?? commentCount)}</span></button>
              <button className={shareOpen ? 'liked' : ''} onClick={openShare}><IcUpload /><span>업로드</span></button>
            </div>
            <div className="vbottom">
              <span className="badge vbadge"><i style={{ background: s.hex }} />{s.name}</span>
              <div className="vt">{item.title}</div>
              <div className="vn">{s.desc} · 이 가게를 위한 병맛 홍보 썰박스</div>
            </div>

            {open && (
              <div className="cmts">
                <div className="cmts-h"><span>댓글 {commentCount}</span><button aria-label="닫기" onClick={() => setOpen(false)}>✕</button></div>
                <div className="cmts-list">
                  {comments.length === 0 && <div className="cmts-empty">첫 댓글을 남겨보세요.</div>}
                  {comments.map((c) => (
                    <div className="cmt" key={c.id}>
                      <div className="who">{c.nickname}{c.is_mine ? ' · 나' : ''}</div>
                      <div className="bd">{c.body ?? '삭제된 댓글'}</div>
                      {c.replies?.map((r) => (
                        <div className="rep" key={r.id}>
                          <div className="who">{r.nickname}{r.is_mine ? ' · 나' : ''}</div>
                          <div className="bd">{r.body ?? '삭제된 댓글'}</div>
                        </div>
                      ))}
                    </div>
                  ))}
                </div>
                <div className="cmts-in">
                  <input
                    placeholder="댓글 달기…" value={text}
                    onChange={(e) => setText(e.target.value)}
                    onKeyDown={(e) => { if (e.key === 'Enter') send() }}
                  />
                  <button disabled={!text.trim()} onClick={send}>게시</button>
                </div>
              </div>
            )}

            {shareOpen && (
              <div className="cmts share">
                <div className="cmts-h"><span>SNS 업로드</span><button aria-label="닫기" onClick={() => setShareOpen(false)}>✕</button></div>
                <div className="share-list">
                  {PLATFORMS.map(({ key, name, Icon, hex }) => {
                    const linked = accounts.some((a) => a.platform === key)
                    return (
                      <button key={key} className="share-row" disabled={upBusy === key} onClick={() => upload(key)}>
                        <span className="sns-ic" style={sc(hex)}><Icon /></span>
                        <span className="share-name">{name}</span>
                        <span className="share-state">{upBusy === key ? '업로드 중…' : linked ? '업로드' : '연동 필요'}</span>
                      </button>
                    )
                  })}
                </div>
                <div className="share-hint">완성된 영상을 연동한 채널로 바로 올려요. 계정 연동은 <b>내 채널 → SNS 연동</b>에서.</div>
              </div>
            )}
          </div>
        </>
      )}
    </div>
  )
}
