'use client'

import { useState, useEffect, useCallback, useRef } from 'react'
import type React from 'react'
import {
  Plus, Copy, RotateCw, Trash2, Eye, EyeOff, RefreshCw,
  AlertTriangle, ExternalLink, MoreHorizontal, Check,
} from 'lucide-react'
import { CustomSelect } from '@/components/ui/custom-select'
import { Field, Textarea, Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Switch } from '@/components/ui/switch'
import { useModels } from '@/hooks/use-models'
import type { H5Page } from '@/lib/database/schema'
import { parseAppSecrets, type AppSecrets, type AppSecretEntry } from '@/lib/h5/app-secrets'
import { H5_THEME_PRESETS, DEFAULT_PRESET_ID } from '@/lib/h5/theme-presets'
import { cn } from '@/lib/utils'

/**
 * H5View — owner 配置 H5 分享页面。
 * 文档式编辑结构（01~05 节），autosave 带 saving/saved/error 状态反馈。
 * 权限固定为 full（后端默认），不暴露选择 UI。
 */

interface H5ViewProps {
  workspaceId: string
}

type SaveState = 'idle' | 'saving' | 'saved' | 'error'

export function H5View({ workspaceId }: H5ViewProps): React.JSX.Element {
  const { models } = useModels()
  const [page, setPage] = useState<H5Page | null>(null)
  const [loading, setLoading] = useState(true)

  // autosave 反馈状态
  const [saveState, setSaveState] = useState<SaveState>('idle')
  const [saveError, setSaveError] = useState<string | null>(null)
  const savedTimer = useRef<ReturnType<typeof setTimeout>>(undefined)
  const lastPatchRef = useRef<Record<string, unknown> | null>(null)

  const [showAddApp, setShowAddApp] = useState(false)
  const [newAppName, setNewAppName] = useState('')
  const [newAppId, setNewAppId] = useState('')
  const [revealedSecrets, setRevealedSecrets] = useState<Record<string, boolean>>({})
  const [regenerateConfirm, setRegenerateConfirm] = useState(false)
  const [disableConfirm, setDisableConfirm] = useState(false)
  // 展开的 APP（⋯ 菜单：品牌主题 + 危险操作）
  const [expandedAppId, setExpandedAppId] = useState<string | null>(null)
  const [debugOpen, setDebugOpen] = useState(false)
  const [pageInfoDraft, setPageInfoDraft] = useState({
    pageTitle: '',
    assistantName: '',
    description: '',
    welcomeMessage: '',
    iconEmoji: '',
  })

  useEffect(() => {
    if (page) {
      setPageInfoDraft({
        pageTitle: page.pageTitle,
        assistantName: page.assistantName,
        description: page.description,
        welcomeMessage: page.welcomeMessage,
        iconEmoji: page.iconEmoji,
      })
    }
  }, [page])

  useEffect(() => () => clearTimeout(savedTimer.current), [])

  const load = useCallback(async () => {
    setLoading(true)
    try {
      const res = await fetch(`/api/h5/pages?workspaceId=${encodeURIComponent(workspaceId)}`)
      if (res.ok) {
        const data = (await res.json()) as H5Page | null
        setPage(data)
      }
    } catch {
      // ignore
    } finally {
      setLoading(false)
    }
  }, [workspaceId])

  useEffect(() => {
    void load()
  }, [load])

  /** 统一 PATCH 写入：管理 saving/saved/error，记录最近一次用于重试 */
  const patch = useCallback(async (body: Record<string, unknown>): Promise<H5Page | null> => {
    if (!page) return null
    setSaveState('saving')
    setSaveError(null)
    lastPatchRef.current = body
    try {
      const res = await fetch(`/api/h5/pages/${page.id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      })
      if (!res.ok) throw new Error(`保存失败（${res.status}）`)
      const data = (await res.json()) as H5Page
      setPage(data)
      setSaveState('saved')
      clearTimeout(savedTimer.current)
      savedTimer.current = setTimeout(() => setSaveState('idle'), 1500)
      return data
    } catch (err) {
      setSaveState('error')
      setSaveError(err instanceof Error ? err.message : '保存失败')
      return null
    }
  }, [page])

  const handleCreate = async () => {
    const res = await fetch('/api/h5/pages', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ workspaceId }),
    })
    if (res.ok) {
      const data = (await res.json()) as H5Page
      setPage(data)
    }
  }

  /** 启用：直接开；停用：需确认（有外部用户被拒的后果） */
  const handleEnable = (nextEnabled: boolean) => {
    if (nextEnabled) {
      void patch({ enabled: 1 })
    } else {
      setDisableConfirm(true)
    }
  }

  const handleConfirmDisable = async () => {
    await patch({ enabled: 0 })
    setDisableConfirm(false)
  }

  const handleRegenerateSlug = async () => {
    if (!page) return
    const res = await fetch(`/api/h5/pages/${page.id}?action=regenerate-slug`, { method: 'POST' })
    if (res.ok) {
      const data = (await res.json()) as H5Page
      setPage(data)
      setRegenerateConfirm(false)
    }
  }

  const handleAddApp = async () => {
    if (!page || !newAppId.trim() || !newAppName.trim()) return
    const data = await patch({
      appSecretOp: { type: 'add', appId: newAppId.trim(), name: newAppName.trim() },
    })
    if (data) {
      setNewAppName('')
      setNewAppId('')
      setShowAddApp(false)
    }
  }

  const handleRemoveApp = async (appId: string) => {
    await patch({ appSecretOp: { type: 'remove', appId } })
    setExpandedAppId(null)
  }

  const handleRotateSecret = async (appId: string) => {
    await patch({ appSecretOp: { type: 'rotate', appId } })
  }

  const handleSetPreset = async (appId: string, presetId: string) => {
    const op = presetId === DEFAULT_PRESET_ID
      ? { type: 'clearThemePreset' as const, appId }
      : { type: 'setThemePreset' as const, appId, presetId }
    await patch({ appSecretOp: op })
  }

  const retryLastPatch = () => {
    if (lastPatchRef.current) void patch(lastPatchRef.current)
  }

  // ── 快速调试状态（Rules of Hooks：放在早期 return 之前） ──
  const [debugAppId, setDebugAppId] = useState('')
  const [debugUserId, setDebugUserId] = useState('u_test_001')
  const [debugUserName, setDebugUserName] = useState('测试用户')
  const [debugJwt, setDebugJwt] = useState('')
  const [debugUrl, setDebugUrl] = useState('')
  const [debugGenerating, setDebugGenerating] = useState(false)
  const [debugError, setDebugError] = useState<string | null>(null)

  /** 浏览器端 HMAC-SHA256 + base64url 签 JWT */
  const generateJWT = useCallback(async (
    payload: Record<string, unknown>,
    secret: string,
  ): Promise<string> => {
    const b64urlBytes = (bytes: Uint8Array) => {
      let bin = ''
      for (const b of bytes) bin += String.fromCharCode(b)
      return btoa(bin).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
    }
    const b64url = (s: string) => b64urlBytes(new TextEncoder().encode(s))

    const header = { alg: 'HS256', typ: 'JWT' }
    const h = b64url(JSON.stringify(header))
    const p = b64url(JSON.stringify(payload))
    const data = `${h}.${p}`

    const encoder = new TextEncoder()
    const key = await crypto.subtle.importKey(
      'raw',
      encoder.encode(secret),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign'],
    )
    const sigBuf = await crypto.subtle.sign('HMAC', key, encoder.encode(data))
    const sigB64url = b64urlBytes(new Uint8Array(sigBuf))

    return `${data}.${sigB64url}`
  }, [])

  const getSecret = useCallback((appId: string): string | null => {
    if (!page) return null
    const secrets: AppSecrets = parseAppSecrets(page.appSecrets)
    const entry = secrets[appId]
    return entry?.secret ?? null
  }, [page])

  const handleGenerateDebugUrl = useCallback(async () => {
    if (!debugAppId || !page) return
    const secret = getSecret(debugAppId)
    if (!secret) {
      setDebugError('未找到该 APP 的密钥')
      return
    }
    setDebugGenerating(true)
    setDebugError(null)
    try {
      const now = Math.floor(Date.now() / 1000)
      const token = await generateJWT(
        {
          iss: debugAppId,
          sub: debugUserId || 'u_anonymous',
          name: debugUserName || undefined,
          iat: now,
          exp: now + 2 * 3600,
        },
        secret,
      )
      const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''
      setDebugJwt(token)
      setDebugUrl(`${baseUrl}/h5/${page.slug}?token=${token}`)
    } catch (err) {
      setDebugError(err instanceof Error ? err.message : '生成失败')
    } finally {
      setDebugGenerating(false)
    }
  }, [debugAppId, debugUserId, debugUserName, getSecret, generateJWT, page])

  useEffect(() => {
    if (!page) return
    const secrets: AppSecrets = parseAppSecrets(page.appSecrets)
    const entries = Object.entries(secrets)
    if (!debugAppId && entries.length > 0) {
      setDebugAppId(entries[0][0])
    }
  }, [page, debugAppId])

  if (loading) {
    return (
      <div className="flex items-center justify-center h-full text-[13px] text-ink-tertiary">
        Loading...
      </div>
    )
  }

  if (!page) {
    return (
      <div className="flex flex-col h-full">
        <div className="flex items-center justify-between h-[52px] px-6 border-b border-hairline shrink-0">
          <span className="text-[20px] font-semibold text-ink font-heading tracking-tight">H5 分享</span>
        </div>
        <div className="flex-1 flex flex-col items-center justify-center gap-4">
          <p className="text-[13px] text-ink-tertiary">该工作区尚未开启 H5 分享</p>
          <Button variant="primary" onClick={() => void handleCreate()}>开启 H5 分享</Button>
        </div>
      </div>
    )
  }

  const shareUrl = typeof window !== 'undefined' ? `${window.location.origin}/h5/${page.slug}` : `/h5/${page.slug}`
  const appSecrets: AppSecrets = parseAppSecrets(page.appSecrets)
  const appEntries = Object.entries(appSecrets)

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="sticky top-0 z-10 flex items-center justify-between h-[52px] px-6 border-b border-hairline bg-surface-1">
        <span className="text-[20px] font-semibold text-ink font-heading tracking-tight">H5 分享</span>
        <div className="flex items-center gap-3">
          <SaveIndicator state={saveState} error={saveError} onRetry={retryLastPatch} />
          {saveState !== 'idle' && <div className="h-4 w-px bg-hairline" />}
          <div className="flex items-center gap-2">
            <span className={cn('text-[11px] font-medium', page.enabled ? 'text-green' : 'text-ink-tertiary')}>
              {page.enabled ? '已启用' : '已停用'}
            </span>
            <Switch
              checked={!!page.enabled}
              onChange={handleEnable}
              size="sm"
              tone="success"
              aria-label="启用 H5 分享"
            />
          </div>
        </div>
      </div>

      {/* 停用确认条 */}
      {disableConfirm && (
        <div className="flex items-center justify-between gap-3 px-6 h-[40px] border-b border-hairline bg-coral/5">
          <span className="text-[11px] text-coral">停用后外部用户将被立即拒绝访问</span>
          <div className="flex items-center gap-1.5">
            <Button size="sm" variant="danger" onClick={() => void handleConfirmDisable()}>确认停用</Button>
            <Button size="sm" variant="ghost" onClick={() => setDisableConfirm(false)}>取消</Button>
          </div>
        </div>
      )}

      {/* Content: 文档式章节 */}
      <div className="flex-1 overflow-y-auto px-6 py-6">
        <div className="max-w-3xl mx-auto space-y-10">

          {/* 01 分享地址 */}
          <section className="space-y-3">
            <SectionTitle index="01" title="分享地址" />
            <div className="flex items-center gap-2">
              <code className="flex-1 text-[12px] font-mono text-ink bg-surface-2 border border-hairline px-3 py-2 rounded-control break-all">
                {shareUrl}
              </code>
              <Button variant="secondary" size="sm" leftIcon={<Copy size={13} />} onClick={() => void navigator.clipboard.writeText(shareUrl)}>
                复制
              </Button>
            </div>
            {regenerateConfirm ? (
              <div className="flex items-center gap-2">
                <span className="text-[11px] text-amber">重新生成后旧地址立即失效</span>
                <Button size="sm" variant="danger" onClick={() => void handleRegenerateSlug()}>确认</Button>
                <Button size="sm" variant="ghost" onClick={() => setRegenerateConfirm(false)}>取消</Button>
              </div>
            ) : (
              <button
                onClick={() => setRegenerateConfirm(true)}
                className="flex items-center gap-1.5 text-[11px] text-ink-tertiary hover:text-ink-muted"
              >
                <RefreshCw size={11} /> 重新生成地址
              </button>
            )}
          </section>

          {/* 02 对外展示 */}
          <section className="space-y-4">
            <SectionTitle index="02" title="对外展示" />
            <div className="grid grid-cols-1 gap-4">
              <Field label="页面名称" helper="显示在顶栏和浏览器标题">
                <Input
                  value={pageInfoDraft.pageTitle}
                  onChange={(e) => setPageInfoDraft((p) => ({ ...p, pageTitle: e.target.value }))}
                  onBlur={() => void patch({ pageTitle: pageInfoDraft.pageTitle })}
                  placeholder="智能体方案名称"
                />
              </Field>
              <Field label="方案名称" helper="显示在欢迎卡（如“你好，我是…”）。不填则用页面名称">
                <Input
                  value={pageInfoDraft.assistantName}
                  onChange={(e) => setPageInfoDraft((p) => ({ ...p, assistantName: e.target.value }))}
                  onBlur={() => void patch({ assistantName: pageInfoDraft.assistantName })}
                  placeholder="如“萃萃”"
                />
              </Field>
              <Field label="方案描述">
                <Textarea
                  value={pageInfoDraft.description}
                  onChange={(e) => setPageInfoDraft((p) => ({ ...p, description: e.target.value }))}
                  onBlur={() => void patch({ description: pageInfoDraft.description })}
                  placeholder="一句话介绍这个智能体方案能做什么"
                  rows={2}
                />
              </Field>
              <Field label="引导语" helper="支持 Markdown，单次回车会保留换行。用户首次进入或新建会话时展示。">
                <Textarea
                  value={pageInfoDraft.welcomeMessage}
                  onChange={(e) => setPageInfoDraft((p) => ({ ...p, welcomeMessage: e.target.value }))}
                  onBlur={() => void patch({ welcomeMessage: pageInfoDraft.welcomeMessage })}
                  placeholder="你好，我是……"
                  rows={4}
                />
              </Field>
              <Field label="图标" helper="填 emoji 或图片 URL">
                <Input
                  value={pageInfoDraft.iconEmoji}
                  onChange={(e) => setPageInfoDraft((p) => ({ ...p, iconEmoji: e.target.value }))}
                  onBlur={() => void patch({ iconEmoji: pageInfoDraft.iconEmoji })}
                  placeholder="✨ 或 https://..."
                />
              </Field>
            </div>
          </section>

          {/* 03 默认模型 */}
          <section className="space-y-3">
            <SectionTitle index="03" title="默认模型" />
            <div className="max-w-sm">
              <CustomSelect
                size="sm"
                value={page.defaultModel}
                onChange={(v) => void patch({ defaultModel: v })}
                options={[
                  { value: '', label: '使用全局默认' },
                  ...models.map((m) => ({ value: m.id, label: m.label })),
                ]}
              />
            </div>
          </section>

          {/* 04 对接应用 */}
          <section className="space-y-3">
            <SectionTitle
              index="04"
              title="对接应用"
              action={
                <Button size="sm" variant="secondary" leftIcon={<Plus size={13} />} onClick={() => setShowAddApp(!showAddApp)}>
                  添加
                </Button>
              }
            />

            {showAddApp && (
              <div className="space-y-3 p-4 rounded-control border border-hairline bg-surface-2">
                <div className="grid grid-cols-2 gap-3">
                  <Field label="名称">
                    <Input value={newAppName} onChange={(e) => setNewAppName(e.target.value)} placeholder="陪伴 APP" />
                  </Field>
                  <Field label="app_id" helper="反域名风格">
                    <Input value={newAppId} onChange={(e) => setNewAppId(e.target.value)} placeholder="com.peiban.app" className="font-mono" />
                  </Field>
                </div>
                <div className="flex justify-end">
                  <Button size="sm" variant="primary" onClick={() => void handleAddApp()} disabled={!newAppId.trim() || !newAppName.trim()}>
                    生成密钥并添加
                  </Button>
                </div>
              </div>
            )}

            {appEntries.length === 0 ? (
              <p className="text-[12px] text-ink-tertiary py-6 text-center border-b border-hairline">
                尚未添加对接应用
              </p>
            ) : (
              <div className="border-b border-hairline">
                {appEntries.map(([appId, entry]: [string, AppSecretEntry]) => {
                  const revealed = revealedSecrets[appId]
                  const maskedSecret = entry.secret.slice(0, 3) + '••••' + entry.secret.slice(-4)
                  const expanded = expandedAppId === appId
                  const preset = H5_THEME_PRESETS.find((p) => p.id === entry.themePreset)
                  return (
                    <div key={appId} className="border-t border-hairline first:border-t-0 py-3">
                      <div className="flex items-center justify-between gap-3">
                        <div className="min-w-0 flex items-center gap-2">
                          {entry.themePreset && preset && (
                            <span
                              className="inline-block w-2 h-2 rounded-full shrink-0"
                              style={{ backgroundColor: preset.theme.accent }}
                              aria-hidden
                            />
                          )}
                          <div className="min-w-0">
                            <div className="text-[13px] font-medium text-ink truncate">{entry.name}</div>
                            <div className="text-[11px] text-ink-tertiary font-mono truncate">{appId}</div>
                          </div>
                        </div>
                        <div className="flex items-center gap-1 shrink-0">
                          <IconButton
                            title={revealed ? '隐藏密钥' : '显示密钥'}
                            onClick={() => setRevealedSecrets((s) => ({ ...s, [appId]: !s[appId] }))}
                          >
                            {revealed ? <EyeOff size={13} /> : <Eye size={13} />}
                          </IconButton>
                          <IconButton title="复制密钥" onClick={() => void navigator.clipboard.writeText(entry.secret)}>
                            <Copy size={13} />
                          </IconButton>
                          <IconButton
                            title="更多操作"
                            active={expanded}
                            onClick={() => setExpandedAppId(expanded ? null : appId)}
                          >
                            <MoreHorizontal size={15} />
                          </IconButton>
                        </div>
                      </div>

                      <code className="block mt-2 text-[11px] font-mono text-ink-muted break-all">
                        {revealed ? entry.secret : maskedSecret}
                      </code>

                      {expanded && (
                        <div className="mt-3 pt-3 border-t border-hairline space-y-3">
                          <Field label="品牌主题">
                            <div className="flex items-center gap-2">
                              <div className="max-w-xs flex-1">
                                <CustomSelect
                                  size="sm"
                                  value={entry.themePreset || DEFAULT_PRESET_ID}
                                  onChange={(v) => void handleSetPreset(appId, v)}
                                  options={H5_THEME_PRESETS.map((p) => ({
                                    value: p.id,
                                    label: `${p.name} · ${p.mode === 'dark' ? '深色' : '浅色'}`,
                                  }))}
                                />
                              </div>
                              {preset && (
                                <span className="text-[11px] text-ink-tertiary truncate">{preset.description}</span>
                              )}
                            </div>
                          </Field>
                          <div className="flex items-center gap-2 pt-1">
                            <Button size="sm" variant="ghost" leftIcon={<RotateCw size={12} />} onClick={() => void handleRotateSecret(appId)}>
                              轮换密钥
                            </Button>
                            <Button size="sm" variant="danger" leftIcon={<Trash2 size={12} />} onClick={() => void handleRemoveApp(appId)}>
                              移除应用
                            </Button>
                          </div>
                        </div>
                      )}
                    </div>
                  )
                })}
              </div>
            )}
          </section>

          {/* 05 开发调试 */}
          <section className="space-y-3">
            <SectionTitle
              index="05"
              title="开发调试"
              action={
                <button
                  onClick={() => setDebugOpen(!debugOpen)}
                  className="text-[11px] text-ink-tertiary hover:text-ink-muted"
                >
                  {debugOpen ? '收起' : '展开'}
                </button>
              }
            />
            {!debugOpen ? (
              <p className="text-[11px] text-ink-tertiary">
                本地生成带 Token 的调试链接，用于联调对接 APP。
              </p>
            ) : appEntries.length === 0 ? (
              <p className="text-[11px] text-ink-tertiary">请先在「对接应用」添加至少一个 APP。</p>
            ) : (
              <div className="space-y-4">
                <div className="grid grid-cols-3 gap-3">
                  <Field label="选择 APP">
                    <CustomSelect
                      size="sm"
                      value={debugAppId}
                      onChange={(v) => { setDebugAppId(v); setDebugJwt(''); setDebugUrl(''); setDebugError(null) }}
                      options={appEntries.map(([appId, entry]) => ({
                        value: appId,
                        label: `${entry.name} (${appId})`,
                      }))}
                    />
                  </Field>
                  <Field label="用户 ID">
                    <Input
                      value={debugUserId}
                      onChange={(e) => { setDebugUserId(e.target.value); setDebugJwt(''); setDebugUrl('') }}
                      placeholder="u_test_001"
                      className="font-mono"
                    />
                  </Field>
                  <Field label="用户名称">
                    <Input
                      value={debugUserName}
                      onChange={(e) => { setDebugUserName(e.target.value); setDebugJwt(''); setDebugUrl('') }}
                      placeholder="测试用户"
                    />
                  </Field>
                </div>
                <div className="flex items-center gap-2">
                  <Button
                    variant="primary"
                    size="sm"
                    loading={debugGenerating}
                    disabled={!debugAppId}
                    onClick={() => void handleGenerateDebugUrl()}
                  >
                    生成调试链接
                  </Button>
                  {(debugUrl || debugJwt) && (
                    <Button
                      variant="ghost"
                      size="sm"
                      onClick={() => { setDebugJwt(''); setDebugUrl(''); setDebugError(null) }}
                    >
                      清空
                    </Button>
                  )}
                </div>
                {debugError && <p className="text-[11px] text-coral">{debugError}</p>}
                {debugUrl && (
                  <div className="space-y-2 p-3 rounded-control border border-hairline bg-surface-2">
                    <div className="flex items-center gap-2">
                      <code className="flex-1 text-[11px] font-mono text-ink break-all leading-relaxed">{debugUrl}</code>
                      <IconButton title="复制链接" onClick={() => void navigator.clipboard.writeText(debugUrl)}>
                        <Copy size={12} />
                      </IconButton>
                      <a
                        href={debugUrl}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="inline-flex items-center gap-1 px-2.5 py-1.5 rounded-control bg-green/10 text-green text-[11px] font-medium hover:bg-green/20 shrink-0"
                      >
                        <ExternalLink size={12} /> 打开
                      </a>
                    </div>
                    <details className="group">
                      <summary className="text-[11px] text-ink-tertiary cursor-pointer hover:text-ink-muted">
                        查看 JWT token
                      </summary>
                      <code className="block mt-1.5 text-[10px] font-mono text-ink-tertiary break-all bg-surface-1 px-2 py-1.5 rounded select-all">
                        {debugJwt}
                      </code>
                    </details>
                  </div>
                )}
              </div>
            )}
          </section>

        </div>
      </div>
    </div>
  )
}

/* ─── 子组件 ─── */

function SectionTitle({
  index,
  title,
  action,
}: {
  index: string
  title: string
  action?: React.ReactNode
}) {
  return (
    <div className="flex items-end justify-between gap-3 pb-2 border-b border-hairline">
      <div className="flex items-baseline gap-2.5">
        <span className="text-[11px] font-mono text-ink-tertiary tabular-nums">{index}</span>
        <h2 className="text-[15px] font-semibold text-ink">{title}</h2>
      </div>
      {action}
    </div>
  )
}

function SaveIndicator({
  state,
  error,
  onRetry,
}: {
  state: SaveState
  error: string | null
  onRetry: () => void
}) {
  if (state === 'idle') return null
  return (
    <div className="flex items-center gap-1 text-[11px]">
      {state === 'saving' && (
        <span className="flex items-center gap-1 text-ink-tertiary">
          <span className="w-1.5 h-1.5 rounded-full bg-ink-tertiary animate-pulse" />
          保存中
        </span>
      )}
      {state === 'saved' && (
        <span className="flex items-center gap-1 text-green">
          <Check size={12} />
          已保存
        </span>
      )}
      {state === 'error' && (
        <button onClick={onRetry} className="flex items-center gap-1 text-coral hover:underline">
          <AlertTriangle size={12} />
          {error ?? '保存失败'} · 重试
        </button>
      )}
    </div>
  )
}

function IconButton({
  title,
  onClick,
  active,
  children,
}: {
  title: string
  onClick: () => void
  active?: boolean
  children: React.ReactNode
}) {
  return (
    <button
      type="button"
      title={title}
      onClick={onClick}
      className={cn(
        'inline-flex items-center justify-center w-7 h-7 rounded-control transition-colors',
        active ? 'bg-surface-3 text-ink' : 'text-ink-tertiary hover:text-ink hover:bg-surface-2',
      )}
    >
      {children}
    </button>
  )
}
