import { useCallback, useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { api, ApiError } from '../api/client'
import { PlacementFlowNav } from '../components/PlacementFlowNav'
import { PlacementScanJobPanel } from '../components/PlacementScanJobPanel'
import { ReplyPersonaPicker } from '../components/ReplyPersonaPicker'
import { useActiveTopic } from '../hooks/useActiveTopic'
import { getReplyPersonaId } from '../lib/brandContext'
import {
  Badge,
  Button,
  Card,
  ErrorText,
  Field,
  Notice,
  Select,
  Textarea,
} from '../components/ui'
import { formatPostedAt, formatScanPostDate, formatUnixNano } from '../lib/formatDate'
import { isActiveJobStatus, isTerminalJobStatus } from '../lib/jobStatus'
import { useIslanderPage } from '../lib/islander'
import type { JobData } from '../types/api'
import {
  OUTREACH_DRAFT_EVENT,
  OUTREACH_DRAFT_LABEL,
  bindOutreachIslanderActions,
  ensureOutreachActionHandlers,
  generateOutreachDraftRemote,
  unbindOutreachIslanderActions,
  type OutreachDraftData,
} from '../lib/islander/outreachActions'


export type ScanPostData = {
  id: string
  graph_node_id: string
  search_tag: string
  query_dimension: string
  external_id: string
  permalink: string
  author: string
  text: string
  priority: string
  placement_score: number
  product_fit_score: number
  solved_by_product: boolean
  source: string
  scan_job_id: string
  outreach_status?: string
  published_reply_id?: string
  published_permalink?: string
  outreach_update_at?: number
  posted_at?: string
  latest_draft?: OutreachDraftData
  replies?: Array<{
    external_id?: string
    author?: string
    text: string
    permalink?: string
    like_count?: number
    posted_at?: string
  }>
  create_at: number
}

const PRIORITY_LABEL: Record<string, string> = {
  gold: '雙軌命中',
  recent: '近期軌',
  relevant: '相關軌',
}

const OUTREACH_STATUS_LABEL: Record<string, string> = {
  pending: '待處理',
  drafted: '已草稿',
  published: '已發送',
  handled: '已處理',
  skipped: '已略過',
}

function outreachTone(status: string): 'success' | 'warning' | 'sky' | 'neutral' {
  if (status === 'published' || status === 'handled') return 'success'
  if (status === 'drafted') return 'sky'
  if (status === 'skipped') return 'warning'
  return 'neutral'
}

function priorityTone(priority: string): 'success' | 'warning' | 'sky' | 'neutral' {
  if (priority === 'gold') return 'success'
  if (priority === 'recent') return 'warning'
  if (priority === 'relevant') return 'sky'
  return 'neutral'
}

ensureOutreachActionHandlers()

function latestDraftState(posts: ScanPostData[]) {
  const drafts: Record<string, OutreachDraftData> = {}
  const selected: Record<string, number> = {}
  const text: Record<string, string> = {}
  for (const post of posts) {
    if (!post.latest_draft) continue
    drafts[post.id] = post.latest_draft
    selected[post.id] = 0
    text[post.id] = post.latest_draft.drafts[0]?.text ?? ''
  }
  return { drafts, selected, text }
}

export function PersonaOutreachPage() {
  const {
    topicId: id,
    topic,
    topics,
    brand,
    topicLabel,
    setTopicId,
    loading: topicLoading,
    error: topicError,
  } = useActiveTopic()
  const [replyPersonaId, setReplyPersonaId] = useState(getReplyPersonaId)

  const [posts, setPosts] = useState<ScanPostData[]>([])
  const [draftsByPost, setDraftsByPost] = useState<Record<string, OutreachDraftData>>({})
  const [selectedDraftIndex, setSelectedDraftIndex] = useState<Record<string, number>>({})
  const [draftTextByPost, setDraftTextByPost] = useState<Record<string, string>>({})
  const [selectedPostIds, setSelectedPostIds] = useState<string[]>([])
  const [generatingPostId, setGeneratingPostId] = useState<string | null>(null)
  const [publishingPostId, setPublishingPostId] = useState<string | null>(null)
  const [patchingPostId, setPatchingPostId] = useState<string | null>(null)
  const [deletingPosts, setDeletingPosts] = useState(false)
  const [priority, setPriority] = useState('')
  const [recent7d, setRecent7d] = useState(true)
  const [productFitMin, setProductFitMin] = useState(70)
  const [scanJob, setScanJob] = useState<JobData | null>(null)
  const [scanJobId, setScanJobId] = useState<string | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState('')

  useIslanderPage({
    title: '獲客台',
    purpose: '檢視海巡貼文、評估 productFitScore、產生獲客留言草稿',
    hints: [
      'gold = 相關軌與近期軌都命中',
      '預設篩 7 天內（gold/recent）與產品可解',
      '可用島民 generateOutreachReply 產生草稿並填入「獲客留言草稿」',
      'Threads API 連線後可用 publishOutreach 發送（需 confirm=true）',
      '手動處理完可標記 handled 或 skipped',
    ],
    suggestions: ['哪幾篇適合留言？', '幫我寫獲客留言', '幫我發送這則留言'],
  })

  const generateForPost = useCallback(
    async (scanPostId: string, count = 2) => {
      if (!id) return null
      setGeneratingPostId(scanPostId)
      setError('')
      try {
        const data = await generateOutreachDraftRemote(id, scanPostId, count, replyPersonaId, topic?.product_id)
        setDraftsByPost((prev) => ({ ...prev, [scanPostId]: data }))
        setSelectedDraftIndex((prev) => ({ ...prev, [scanPostId]: 0 }))
        const primary = data.drafts[0]?.text ?? ''
        setDraftTextByPost((prev) => ({ ...prev, [scanPostId]: primary }))
        setPosts((prev) =>
          prev.map((post) =>
            post.id === scanPostId
              ? { ...post, outreach_status: 'drafted', latest_draft: data }
              : post,
          ),
        )
        return data
      } catch (e) {
        setError(e instanceof ApiError ? e.message : '產生獲客留言失敗')
        return null
      } finally {
        setGeneratingPostId(null)
      }
    },
    [id, replyPersonaId, topic?.product_id],
  )

  const publishForIslander = useCallback(
    async (scanPostId: string, text: string) => {
      if (!id) return null
      const result = await api.post<{ message: string; outreach_status: string; published_permalink?: string }>(
        `/api/v1/placement/topics/${encodeURIComponent(id)}/outreach-drafts/publish`,
        { scan_post_id: scanPostId, text: text.trim(), confirm: true },
        { auth: true },
      )
      setPosts((prev) =>
        prev.map((p) =>
          p.id === scanPostId
            ? {
                ...p,
                outreach_status: result.outreach_status,
                published_permalink: result.published_permalink,
              }
            : p,
        ),
      )
      return result
    },
    [id],
  )

  const patchForIslander = useCallback(
    async (scanPostId: string, status: string) => {
      if (!id) return null
      const updated = await api.patch<ScanPostData>(
        `/api/v1/placement/topics/${encodeURIComponent(id)}/scan-posts/${encodeURIComponent(scanPostId)}`,
        { outreach_status: status },
        { auth: true },
      )
      setPosts((prev) => prev.map((p) => (p.id === updated.id ? { ...p, ...updated } : p)))
      return updated
    },
    [id],
  )

  useEffect(() => {
    if (!id) return
    bindOutreachIslanderActions({
      brandId: id,
      generate: generateForPost,
      publish: publishForIslander,
      patchOutreach: patchForIslander,
    })
    return () => unbindOutreachIslanderActions()
  }, [id, generateForPost, publishForIslander, patchForIslander])

  useEffect(() => {
    const onDraft = (event: Event) => {
      const detail = (event as CustomEvent<{ scanPostId: string; draft: OutreachDraftData }>).detail
      if (!detail?.scanPostId || !detail.draft) return
      setDraftsByPost((prev) => ({ ...prev, [detail.scanPostId]: detail.draft }))
      setSelectedDraftIndex((prev) => ({ ...prev, [detail.scanPostId]: 0 }))
      const primary = detail.draft.drafts[0]?.text ?? ''
      setDraftTextByPost((prev) => ({ ...prev, [detail.scanPostId]: primary }))
    }
    window.addEventListener(OUTREACH_DRAFT_EVENT, onDraft)
    return () => window.removeEventListener(OUTREACH_DRAFT_EVENT, onDraft)
  }, [])

  const load = useCallback(async (opts?: { silent?: boolean }) => {
    if (!id) return
    if (!opts?.silent) {
      setLoading(true)
      setError('')
    }
    try {
      const postsData = await api.get<{ list: ScanPostData[]; total: number }>(
          `/api/v1/placement/topics/${encodeURIComponent(id)}/scan-posts`,
          {
            auth: true,
            query: {
              priority: priority || undefined,
              recent_7d: recent7d,
              product_fit_min: productFitMin > 0 ? productFitMin : undefined,
              limit: 100,
            },
          },
        )
      const nextPosts = postsData.list ?? []
      setPosts(nextPosts)
      setSelectedPostIds((prev) => prev.filter((postId) => nextPosts.some((post) => post.id === postId)))
      const restored = latestDraftState(nextPosts)
      const visibleIds = new Set(nextPosts.map((post) => post.id))
      setDraftsByPost((prev) => {
        const next = { ...restored.drafts }
        for (const [postId, draft] of Object.entries(prev)) {
          if (visibleIds.has(postId) && !next[postId]) next[postId] = draft
        }
        return next
      })
      setSelectedDraftIndex((prev) => {
        const next = { ...restored.selected }
        for (const [postId, index] of Object.entries(prev)) {
          if (visibleIds.has(postId) && next[postId] === undefined) next[postId] = index
        }
        return next
      })
      setDraftTextByPost((prev) => {
        const next = { ...restored.text }
        for (const [postId, value] of Object.entries(prev)) {
          if (visibleIds.has(postId) && value.trim()) next[postId] = value
        }
        return next
      })
    } catch (e) {
      if (!opts?.silent) {
        setError(e instanceof ApiError ? e.message : '載入獲客台失敗')
      }
    } finally {
      if (!opts?.silent) {
        setLoading(false)
      }
    }
  }, [id, priority, recent7d, productFitMin])

  const loadActiveScanJob = useCallback(async () => {
    if (!id) return
    const data = await api.get<{ list: JobData[] }>('/api/v1/jobs', {
      auth: true,
      query: { page: 1, pageSize: 20, scope: 'placement_topic', scope_id: id },
    })
    const active = (data.list ?? []).find(
      (job) => job.template_type === 'placement-scan' && isActiveJobStatus(job.status),
    )
    setScanJob(active ?? null)
    setScanJobId(active?.id ?? null)
  }, [id])

  const refreshScanJob = useCallback(
    async (jobId: string) => {
      const job = await api.get<JobData>(`/api/v1/jobs/${encodeURIComponent(jobId)}`, { auth: true })
      if (isTerminalJobStatus(job.status)) {
        setScanJob(null)
        setScanJobId(null)
        if (job.status === 'succeeded') {
          await load({ silent: true })
        }
        return
      }
      setScanJob(job)
      setScanJobId(job.id)
      await load({ silent: true })
    },
    [load],
  )

  useEffect(() => {
    if (!id) return
    setPosts([])
    setDraftsByPost({})
    setSelectedDraftIndex({})
    setDraftTextByPost({})
    setSelectedPostIds([])
    setScanJob(null)
    setScanJobId(null)
    setError('')
    loadActiveScanJob().catch(() => undefined)
  }, [id, loadActiveScanJob])

  useEffect(() => {
    load().catch(() => undefined)
  }, [load])

  useEffect(() => {
    if (!scanJobId) return
    const timer = window.setInterval(() => {
      refreshScanJob(scanJobId).catch(() => undefined)
    }, 3000)
    return () => window.clearInterval(timer)
  }, [scanJobId, refreshScanJob])

  const stats = useMemo(() => {
    const gold = posts.filter((p) => p.priority === 'gold').length
    const solved = posts.filter((p) => p.solved_by_product).length
    return { gold, solved, total: posts.length }
  }, [posts])

  const updatePost = (updated: ScanPostData) => {
    setPosts((prev) => prev.map((p) => (p.id === updated.id ? { ...p, ...updated } : p)))
  }

  const togglePostSelection = (postId: string, checked: boolean) => {
    setSelectedPostIds((prev) => {
      if (checked) {
        return prev.includes(postId) ? prev : [...prev, postId]
      }
      return prev.filter((id) => id !== postId)
    })
  }

  const setAllVisibleSelected = (checked: boolean) => {
    setSelectedPostIds(checked ? posts.map((post) => post.id) : [])
  }

  const deleteSelectedPosts = async () => {
    if (!id || selectedPostIds.length === 0) return
    const ok = window.confirm(`確定刪除 ${selectedPostIds.length} 篇海巡貼文？此操作會從獲客台移除。`)
    if (!ok) return
    const ids = [...selectedPostIds]
    setDeletingPosts(true)
    setError('')
    try {
      await api.post<{ deleted_count: number }>(
        `/api/v1/placement/topics/${encodeURIComponent(id)}/scan-posts/batch-delete`,
        { post_ids: ids },
        { auth: true },
      )
      const idSet = new Set(ids)
      setPosts((prev) => prev.filter((post) => !idSet.has(post.id)))
      setSelectedPostIds((prev) => prev.filter((postId) => !idSet.has(postId)))
      setDraftsByPost((prev) => {
        const next = { ...prev }
        ids.forEach((postId) => delete next[postId])
        return next
      })
      setSelectedDraftIndex((prev) => {
        const next = { ...prev }
        ids.forEach((postId) => delete next[postId])
        return next
      })
      setDraftTextByPost((prev) => {
        const next = { ...prev }
        ids.forEach((postId) => delete next[postId])
        return next
      })
    } catch (e) {
      setError(e instanceof ApiError ? e.message : '刪除貼文失敗')
    } finally {
      setDeletingPosts(false)
    }
  }

  const patchOutreach = async (postId: string, outreachStatus: string) => {
    if (!id) return
    setPatchingPostId(postId)
    setError('')
    try {
      const updated = await api.patch<ScanPostData>(
        `/api/v1/placement/topics/${encodeURIComponent(id)}/scan-posts/${encodeURIComponent(postId)}`,
        { outreach_status: outreachStatus },
        { auth: true },
      )
      updatePost(updated)
    } catch (e) {
      setError(e instanceof ApiError ? e.message : '更新狀態失敗')
    } finally {
      setPatchingPostId(null)
    }
  }

  const publishDraft = async (postId: string, text: string) => {
    if (!id || !text.trim()) return
    const ok = window.confirm('確定透過 Threads API 發送這則獲客留言？')
    if (!ok) return
    setPublishingPostId(postId)
    setError('')
    try {
      const result = await api.post<{
        scan_post_id: string
        outreach_status: string
        published_permalink?: string
        message: string
      }>(
        `/api/v1/placement/topics/${encodeURIComponent(id)}/outreach-drafts/publish`,
        { scan_post_id: postId, text: text.trim(), confirm: true },
        { auth: true },
      )
      setPosts((prev) =>
        prev.map((p) =>
          p.id === postId
            ? {
                ...p,
                outreach_status: result.outreach_status,
                published_permalink: result.published_permalink,
              }
            : p,
        ),
      )
    } catch (e) {
      setError(e instanceof ApiError ? e.message : '發送留言失敗')
    } finally {
      setPublishingPostId(null)
    }
  }

  const selectDraft = (postId: string, index: number) => {
    const draft = draftsByPost[postId]
    if (!draft) return
    const text = draft.drafts[index]?.text ?? ''
    setSelectedDraftIndex((prev) => ({ ...prev, [postId]: index }))
    setDraftTextByPost((prev) => ({ ...prev, [postId]: text }))
  }

  if (!topicLoading && !id) {
    return (
      <div className="mx-auto w-full max-w-6xl space-y-6">
        <PlacementFlowNav
          active="outreach"
          topicId=""
          topics={topics}
          onTopicChange={setTopicId}
          topicLoading={topicLoading}
        />
        <Card>
          <p className="text-sm text-ink-secondary">
            請先在
            <Link to="/placement/topics" className="mx-1 font-semibold text-brand hover:underline">
              找 TA
            </Link>
            建立主題，再進入留言工作台。
          </p>
        </Card>
      </div>
    )
  }

  const brandName = brand?.display_name?.trim() || topic?.brand_display_name?.trim() || '未命名品牌'
  const allVisibleSelected = posts.length > 0 && selectedPostIds.length === posts.length

  return (
    <div className="mx-auto w-full max-w-6xl space-y-6">
      <PlacementFlowNav
        active="outreach"
        topicId={id}
        topics={topics}
        onTopicChange={setTopicId}
        topicLoading={topicLoading}
      />

      <header className="space-y-2">
        <p className="display-en text-[10px] font-bold tracking-[0.16em] text-brand uppercase">
          Outreach
        </p>
        <h1 className="text-2xl font-black text-ink">找 TA · {topicLabel || brandName}</h1>
        {topicLabel ? (
          <p className="text-sm text-muted">品牌：{brandName}</p>
        ) : null}
        <p className="max-w-2xl text-sm leading-relaxed text-ink-secondary">
          海巡結果依 productFitScore 與雙軌優先級排序。產生草稿後可透過 Threads API 發送，或手動貼文後標記處理狀態。
        </p>
        <div className="flex flex-wrap items-center gap-2">
          <Badge tone="sky">共 {stats.total} 篇</Badge>
          <Badge tone="success">gold {stats.gold}</Badge>
          <Badge tone="brand">產品可解 {stats.solved}</Badge>
        </div>
      </header>

      {scanJob ? (
        <Card className="space-y-3 p-5">
          <p className="text-xs font-bold text-brand">此主題海巡進度</p>
          <PlacementScanJobPanel job={scanJob} />
        </Card>
      ) : null}

      <section className="ac-persona-panel grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
        <ReplyPersonaPicker value={replyPersonaId} onChange={setReplyPersonaId} />
        <Field label="優先級">
          <Select value={priority} onChange={(e) => setPriority(e.target.value)}>
            <option value="">全部</option>
            <option value="gold">gold（雙軌命中）</option>
            <option value="recent">recent（近期軌，含雙軌命中）</option>
            <option value="relevant">relevant（相關軌，含雙軌命中）</option>
          </Select>
        </Field>
        <Field label="產品匹配下限">
          <Select
            value={String(productFitMin)}
            onChange={(e) => setProductFitMin(Number(e.target.value))}
          >
            <option value="0">不限</option>
            <option value="40">≥ 40</option>
            <option value="70">≥ 70（建議）</option>
            <option value="85">≥ 85</option>
          </Select>
        </Field>
        <Field label="近期窗口">
          <Select
            value={recent7d ? '7d' : 'all'}
            onChange={(e) => setRecent7d(e.target.value === '7d')}
          >
            <option value="7d">7 天內（gold/recent）</option>
            <option value="all">含 30 天補充</option>
          </Select>
        </Field>
      </section>

      {posts.length > 0 ? (
        <Card className="flex flex-wrap items-center justify-between gap-3 p-4">
          <label className="inline-flex items-center gap-2 text-sm font-semibold text-ink">
            <input
              type="checkbox"
              className="h-4 w-4 accent-[var(--hx-brand)]"
              checked={allVisibleSelected}
              onChange={(e) => setAllVisibleSelected(e.target.checked)}
            />
            選取目前列表
          </label>
          <div className="flex flex-wrap items-center gap-2">
            <span className="text-xs text-muted">已選 {selectedPostIds.length} 篇</span>
            <Button
              variant="danger"
              disabled={selectedPostIds.length === 0 || deletingPosts}
              onClick={() => void deleteSelectedPosts()}
            >
              {deletingPosts ? '刪除中…' : '批量刪除'}
            </Button>
          </div>
        </Card>
      ) : null}

      {loading ? (
        <p className="text-sm text-ink-secondary">載入海巡結果…</p>
      ) : posts.length === 0 ? (
        <Notice
          tone="info"
          title="尚無海巡結果"
          message="請先到研究頁勾選節點並執行「雙軌海巡」。"
        />
      ) : (
        <div className="grid gap-4">
          {posts.map((post) => {
            const draft = draftsByPost[post.id]
            const draftIndex = selectedDraftIndex[post.id] ?? 0
            const draftText = draftTextByPost[post.id] ?? ''
            const busy = generatingPostId === post.id
            const publishing = publishingPostId === post.id
            const patching = patchingPostId === post.id
            const status = post.outreach_status || 'pending'
            const canPublish = status !== 'published' && status !== 'handled' && status !== 'skipped'
            const postDateLabel = formatScanPostDate(post)
            const outreachUpdatedLabel = formatUnixNano(post.outreach_update_at)

            return (
              <article
                key={post.id}
                className="ac-island-card space-y-3 p-5"
                data-islander-label={`獲客貼文 ${post.author}`}
                data-scan-post-id={post.id}
              >
                <div className="flex flex-wrap items-center gap-2">
                  <label className="inline-flex items-center gap-2 text-xs font-semibold text-ink-secondary">
                    <input
                      type="checkbox"
                      className="h-4 w-4 accent-[var(--hx-brand)]"
                      checked={selectedPostIds.includes(post.id)}
                      onChange={(e) => togglePostSelection(post.id, e.target.checked)}
                    />
                    選取
                  </label>
                  <Badge tone={priorityTone(post.priority)}>
                    {PRIORITY_LABEL[post.priority] ?? post.priority}
                  </Badge>
                  <Badge tone={post.solved_by_product ? 'success' : 'warning'}>
                    產品匹配 {post.product_fit_score}
                  </Badge>
                  <Badge tone="neutral">{post.search_tag}</Badge>
                  <Badge tone={outreachTone(status)}>
                    {OUTREACH_STATUS_LABEL[status] ?? status}
                  </Badge>
                  <span className="text-xs text-muted">@{post.author}</span>
                  {postDateLabel ? (
                    <span className="text-xs font-medium text-ink-secondary">
                      發文 {postDateLabel}
                    </span>
                  ) : (
                    <span className="text-xs font-medium text-muted">貼文時間未知</span>
                  )}
                </div>
                <p className="text-sm leading-relaxed text-ink">{post.text}</p>
                {post.replies?.length ? (
                  <details className="rounded-lg border border-line/50 bg-surface/40 p-3 text-sm">
                    <summary className="cursor-pointer font-semibold text-ink">
                      留言參考（{post.replies.length}）
                    </summary>
                    <ul className="mt-2 space-y-2">
                      {post.replies.slice(0, 5).map((reply, index) => {
                        const replyDate = formatPostedAt(reply.posted_at)
                        return (
                          <li key={`${post.id}-reply-${index}`} className="text-ink-secondary">
                            {reply.author ? (
                              <span className="font-medium text-ink">@{reply.author} </span>
                            ) : null}
                            {replyDate ? (
                              <span className="mr-1 text-xs text-muted">{replyDate}</span>
                            ) : null}
                            {reply.text}
                          </li>
                        )
                      })}
                    </ul>
                  </details>
                ) : null}
                <div className="flex flex-wrap items-center gap-3 text-xs text-muted">
                  <span>置入分 {post.placement_score}</span>
                  <span>{post.query_dimension}</span>
                  <span>{post.source}</span>
                  {outreachUpdatedLabel ? <span>處理更新 {outreachUpdatedLabel}</span> : null}
                  <span className="font-mono text-[10px]">id:{post.id.slice(0, 8)}</span>
                  {post.permalink ? (
                    <a href={post.permalink} target="_blank" rel="noreferrer" className="ac-link">
                      開啟貼文
                    </a>
                  ) : null}
                  {post.published_permalink ? (
                    <a
                      href={post.published_permalink}
                      target="_blank"
                      rel="noreferrer"
                      className="ac-link"
                    >
                      已發留言
                    </a>
                  ) : null}
                </div>

                <div className="flex flex-wrap gap-2">
                  <Button
                    variant="soft"
                    disabled={busy || patching}
                    data-islander-label="產生獲客留言"
                    onClick={() => void generateForPost(post.id)}
                  >
                    {busy ? '產生中…' : '產生獲客留言'}
                  </Button>
                  {status !== 'handled' ? (
                    <Button
                      variant="ghost"
                      disabled={patching}
                      data-islander-label="標記已處理"
                      onClick={() => void patchOutreach(post.id, 'handled')}
                    >
                      {patching ? '更新中…' : '標記已處理'}
                    </Button>
                  ) : null}
                  {status !== 'skipped' ? (
                    <Button
                      variant="ghost"
                      disabled={patching}
                      onClick={() => void patchOutreach(post.id, 'skipped')}
                    >
                      略過
                    </Button>
                  ) : null}
                </div>

                {draft ? (
                  <div className="space-y-3 rounded-xl border border-line/60 bg-surface/50 p-4">
                    <div className="flex flex-wrap items-center gap-2 text-xs text-muted">
                      <span>相關度 {Math.round(draft.relevance * 100)}%</span>
                      {draft.reason ? <span>· {draft.reason}</span> : null}
                    </div>
                    {draft.drafts.length > 1 ? (
                      <div className="flex flex-wrap gap-2">
                        {draft.drafts.map((item, index) => (
                          <Button
                            key={`${post.id}-draft-${index}`}
                            variant={draftIndex === index ? 'primary' : 'ghost'}
                            className="text-xs"
                            onClick={() => selectDraft(post.id, index)}
                          >
                            切角 {index + 1}
                            {item.angle ? ` · ${item.angle}` : ''}
                          </Button>
                        ))}
                      </div>
                    ) : null}
                    {draft.drafts[draftIndex]?.rationale ? (
                      <p className="text-xs text-muted">{draft.drafts[draftIndex].rationale}</p>
                    ) : null}
                    <Field label={OUTREACH_DRAFT_LABEL} hint="島民可用 fill 或 applyDraft 填入此欄">
                      <Textarea
                        rows={5}
                        value={draftText}
                        data-islander-label={OUTREACH_DRAFT_LABEL}
                        onChange={(e) =>
                          setDraftTextByPost((prev) => ({ ...prev, [post.id]: e.target.value }))
                        }
                      />
                    </Field>
                    {canPublish && draftText.trim() ? (
                      <Button
                        variant="primary"
                        disabled={publishing}
                        data-islander-label="發送獲客留言"
                        onClick={() => void publishDraft(post.id, draftText)}
                      >
                        {publishing ? '發送中…' : '透過 Threads API 發送'}
                      </Button>
                    ) : null}
                  </div>
                ) : draftText.trim() && canPublish ? (
                  <div className="space-y-3 rounded-xl border border-line/60 bg-surface/50 p-4">
                    <Field label={OUTREACH_DRAFT_LABEL}>
                      <Textarea
                        rows={5}
                        value={draftText}
                        data-islander-label={OUTREACH_DRAFT_LABEL}
                        onChange={(e) =>
                          setDraftTextByPost((prev) => ({ ...prev, [post.id]: e.target.value }))
                        }
                      />
                    </Field>
                    <Button
                      variant="primary"
                      disabled={publishing}
                      onClick={() => void publishDraft(post.id, draftText)}
                    >
                      {publishing ? '發送中…' : '透過 Threads API 發送'}
                    </Button>
                  </div>
                ) : null}
              </article>
            )
          })}
        </div>
      )}

      <ErrorText message={topicError || error} />
    </div>
  )
}
