import React, { useEffect, useState } from 'react'
import { createRoot } from 'react-dom/client'
import { createClient } from '@supabase/supabase-js'
// ===== src/supabase.js =====

const url = import.meta.env?.VITE_SUPABASE_URL || 'https://pqzpbwndrlsmqrylcnum.supabase.co'
const key = import.meta.env?.VITE_SUPABASE_KEY || 'sb_publishable_nyODJxDIcicncI3gyIoljg_caIVxYZ5'

const supabase = createClient(url, key)

// ===== src/lib.js =====

const PROJECT_TYPES = {
  tender:     { label: '標案',  color: '#B4552D' },
  commission: { label: '委託',  color: '#2D6A4F' },
  community:  { label: '社造',  color: '#B07D2B' },
  award:      { label: '獎項',  color: '#7B4B94' },
  research:   { label: '研究',  color: '#2B5D8A' },
  teaching:   { label: '教學',  color: '#4A5568' },
}

const STAGE_TEMPLATES = {
  tender:     ['投標準備', '得標簽約', '履約執行', '驗收結案'],
  commission: ['需求確認', '提案', '設計', '完稿', '輸出／施作', '結案'],
  community:  ['企劃', '場次執行', '紀錄', '結案'],
  award:      ['資格確認', '資料準備', '送件', '結果追蹤'],
  research:   ['研究設計', '資料蒐集', '撰稿', '投稿', '審查修訂', '發表'],
  teaching:   ['課程規劃', '開課執行', '評量', '結課歸檔'],
}

const PROJECT_STATUS = {
  not_started: '尚未開始', active: '進行中', waiting_client: '等待客戶',
  paused: '暫停', done: '已完成', cancelled: '已取消', archived: '已封存',
}

const REVIEW_STATUS = {
  draft:    { label: '草稿',   color: '#8A8A8A' },
  pending:  { label: '待審核', color: '#B07D2B' },
  revising: { label: '修改中', color: '#2B5D8A' },
  approved: { label: '已核准', color: '#2D6A4F' },
  rejected: { label: '已退回', color: '#B4552D' },
  archived: { label: '已封存', color: '#666' },
}

const VARIANTS = {
  proposal: '提案', design: '設計稿', final: '完稿', output: '輸出檔',
  construction: '施作圖', photo: '照片紀錄', doc: '文件',
}

const SOURCES = { client: '客戶提供', self: '自製', vendor: '協力廠回傳' }

// 年季編號前綴，例如 2026-07 → 26Q3
function quarterPrefix(d = new Date()) {
  const yy = String(d.getFullYear()).slice(2)
  const q = Math.floor(d.getMonth() / 3) + 1
  return `${yy}Q${q}`
}

function fmtSize(bytes) {
  if (!bytes) return ''
  const units = ['B', 'KB', 'MB', 'GB']
  let i = 0, n = bytes
  while (n >= 1024 && i < units.length - 1) { n /= 1024; i++ }
  return `${n.toFixed(n >= 100 || i === 0 ? 0 : 1)} ${units[i]}`
}

function fmtDate(s) {
  if (!s) return ''
  return new Date(s).toLocaleDateString('zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit' })
}

async function audit(action, targetType, targetId, detail = {}) {
  const { data: { user } } = await supabase.auth.getUser()
  await supabase.from('audit_log').insert({
    user_id: user?.id, action, target_type: targetType, target_id: String(targetId), detail,
  })
}

// 上傳一個檔案成為資產的新版本（資產不存在則先建立）
async function uploadVersion({ file, projectId, stageId, assetId, displayName, source, variant, note }) {
  const { data: { user } } = await supabase.auth.getUser()

  let asset = null
  if (assetId) {
    const { data } = await supabase.from('assets').select('*').eq('id', assetId).single()
    asset = data
  } else {
    const { data, error } = await supabase.from('assets').insert({
      project_id: projectId, stage_id: stageId || null,
      display_name: displayName || file.name,
      source: source || 'self', variant: variant || 'design',
      created_by: user?.id,
    }).select().single()
    if (error) throw error
    asset = data
  }

  const { data: versions } = await supabase.from('asset_versions')
    .select('version_no').eq('asset_id', asset.id).order('version_no', { ascending: false }).limit(1)
  const nextNo = (versions?.[0]?.version_no || 0) + 1

  const ext = file.name.includes('.') ? file.name.split('.').pop() : 'bin'
  const path = `${asset.id}/v${nextNo}/${crypto.randomUUID()}.${ext}`

  const { error: upErr } = await supabase.storage.from('assets').upload(path, file)
  if (upErr) throw upErr

  const { data: ver, error: verErr } = await supabase.from('asset_versions').insert({
    asset_id: asset.id, version_no: nextNo, storage_path: path,
    file_name: file.name, file_type: ext.toLowerCase(), file_size: file.size,
    note: note || '', uploaded_by: user?.id,
  }).select().single()
  if (verErr) throw verErr

  await supabase.from('assets').update({ current_version_id: ver.id }).eq('id', asset.id)
  await audit('upload_version', 'asset', asset.id, { version: nextNo, file: file.name })
  return { asset, version: ver }
}

async function signedUrl(path) {
  const { data, error } = await supabase.storage.from('assets').createSignedUrl(path, 3600)
  if (error) throw error
  return data.signedUrl
}

// ===== src/views/Login.jsx =====


function Login() {
  const [mode, setMode] = useState('login')
  const [email, setEmail] = useState('')
  const [pw, setPw] = useState('')
  const [msg, setMsg] = useState('')
  const [busy, setBusy] = useState(false)

  async function submit(e) {
    e.preventDefault()
    setBusy(true); setMsg('')
    try {
      if (mode === 'login') {
        const { error } = await supabase.auth.signInWithPassword({ email, password: pw })
        if (error) throw error
      } else {
        const { error } = await supabase.auth.signUp({ email, password: pw })
        if (error) throw error
        setMsg('註冊成功。請到信箱點擊確認連結後再登入。')
        setMode('login')
      }
    } catch (err) {
      setMsg(err.message === 'Invalid login credentials' ? '帳號或密碼錯誤' : err.message)
    } finally { setBusy(false) }
  }

  return (
    <div className="login-wrap">
      <form className="login-card" onSubmit={submit}>
        <h1>SynoFlow</h1>
        <p>工作室專案與數位資產管理</p>
        <div className="field">
          <label>Email</label>
          <input type="email" value={email} onChange={e => setEmail(e.target.value)} required />
        </div>
        <div className="field">
          <label>密碼</label>
          <input type="password" value={pw} onChange={e => setPw(e.target.value)} required minLength={6} />
        </div>
        <button className="btn primary" style={{ width: '100%' }} disabled={busy}>
          {mode === 'login' ? '登入' : '註冊'}
        </button>
        <div style={{ marginTop: 14, textAlign: 'center' }}>
          <button type="button" className="btn ghost" onClick={() => setMode(mode === 'login' ? 'signup' : 'login')}>
            {mode === 'login' ? '第一次使用？註冊帳號' : '已有帳號？登入'}
          </button>
        </div>
        {msg && <div className="err">{msg}</div>}
      </form>
    </div>
  )
}

// ===== src/views/Projects.jsx =====



function Projects() {
  const [projects, setProjects] = useState([])
  const [customers, setCustomers] = useState([])
  const [filter, setFilter] = useState('all')
  const [q, setQ] = useState('')
  const [showNew, setShowNew] = useState(false)
  const [loading, setLoading] = useState(true)

  async function load() {
    setLoading(true)
    const [{ data: ps }, { data: cs }] = await Promise.all([
      supabase.from('projects').select('*, customers(name)').order('created_at', { ascending: false }),
      supabase.from('customers').select('*').order('name'),
    ])
    setProjects(ps || []); setCustomers(cs || []); setLoading(false)
  }
  useEffect(() => { load() }, [])

  const shown = projects.filter(p =>
    (filter === 'all' || p.type === filter) &&
    (!q || p.name.includes(q) || p.code.includes(q) || (p.customers?.name || '').includes(q))
  )

  return (
    <>
      <div className="page-head">
        <h1>專案</h1>
        <button className="btn primary" onClick={() => setShowNew(true)}>＋ 新增專案</button>
      </div>
      <div className="toolbar">
        <div className="chips">
          <button className={'chip' + (filter === 'all' ? ' on' : '')} onClick={() => setFilter('all')}>全部</button>
          {Object.entries(PROJECT_TYPES).map(([k, v]) => (
            <button key={k} className={'chip' + (filter === k ? ' on' : '')} onClick={() => setFilter(k)}>{v.label}</button>
          ))}
        </div>
        <input type="search" placeholder="搜尋名稱／編號／客戶" value={q} onChange={e => setQ(e.target.value)} />
      </div>

      {loading ? <div className="empty">載入中…</div> :
        shown.length === 0 ? <div className="empty">還沒有專案，按「＋ 新增專案」開始。</div> : (
          <div className="grid">
            {shown.map(p => (
              <div key={p.id} className="card" onClick={() => (window.location.hash = `#/projects/${p.id}`)}>
                <div className="code">{p.code}</div>
                <h3>{p.name}</h3>
                <div className="meta">
                  <span className="badge" style={{ background: PROJECT_TYPES[p.type].color }}>{PROJECT_TYPES[p.type].label}</span>
                  <span>{PROJECT_STATUS[p.status]}</span>
                  {p.customers?.name && <span>· {p.customers.name}</span>}
                  {p.due_date && <span>· 截止 {fmtDate(p.due_date)}</span>}
                </div>
              </div>
            ))}
          </div>
        )}

      {showNew && <NewProject customers={customers} onClose={() => setShowNew(false)} onDone={() => { setShowNew(false); load() }} />}
    </>
  )
}

function NewProject({ customers, onClose, onDone }) {
  const [name, setName] = useState('')
  const [type, setType] = useState('commission')
  const [customerId, setCustomerId] = useState('')
  const [newCustomer, setNewCustomer] = useState('')
  const [dueDate, setDueDate] = useState('')
  const [err, setErr] = useState('')
  const [busy, setBusy] = useState(false)
  const code = `${quarterPrefix()}_${name || '…'}`

  async function submit(e) {
    e.preventDefault(); setBusy(true); setErr('')
    try {
      const { data: { user } } = await supabase.auth.getUser()
      let cid = customerId || null
      if (!cid && newCustomer.trim()) {
        const { data, error } = await supabase.from('customers').insert({ name: newCustomer.trim() }).select().single()
        if (error) throw error
        cid = data.id
      }
      const { data: proj, error } = await supabase.from('projects').insert({
        code: `${quarterPrefix()}_${name.trim()}`, name: name.trim(), type,
        customer_id: cid, due_date: dueDate || null, created_by: user?.id,
      }).select().single()
      if (error) throw error
      const stages = STAGE_TEMPLATES[type].map((s, i) => ({
        project_id: proj.id, name: s, seq: i, status: i === 0 ? 'active' : 'pending',
      }))
      await supabase.from('project_stages').insert(stages)
      await audit('create_project', 'project', proj.id, { code: proj.code })
      onDone()
    } catch (e2) {
      setErr(e2.message.includes('duplicate') ? '同季已有相同名稱的專案' : e2.message)
      setBusy(false)
    }
  }

  return (
    <div className="overlay" onClick={e => e.target === e.currentTarget && onClose()}>
      <form className="modal" onSubmit={submit}>
        <h2>新增專案</h2>
        <div className="field">
          <label>專案名稱（編號將是 {code}）</label>
          <input value={name} onChange={e => setName(e.target.value)} required autoFocus />
        </div>
        <div className="field">
          <label>業務線</label>
          <div className="chips">
            {Object.entries(PROJECT_TYPES).map(([k, v]) => (
              <button type="button" key={k} className={'chip' + (type === k ? ' on' : '')} onClick={() => setType(k)}>{v.label}</button>
            ))}
          </div>
          <div className="muted" style={{ marginTop: 6 }}>階段：{STAGE_TEMPLATES[type].join(' → ')}</div>
        </div>
        <div className="row2">
          <div className="field">
            <label>客戶（可留空）</label>
            <select value={customerId} onChange={e => setCustomerId(e.target.value)}>
              <option value="">— 選擇 —</option>
              {customers.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
            </select>
          </div>
          <div className="field">
            <label>或新增客戶</label>
            <input value={newCustomer} onChange={e => setNewCustomer(e.target.value)} placeholder="客戶名稱" disabled={!!customerId} />
          </div>
        </div>
        <div className="field">
          <label>截止日（可留空）</label>
          <input type="date" value={dueDate} onChange={e => setDueDate(e.target.value)} />
        </div>
        {err && <div className="err">{err}</div>}
        <div className="actions">
          <button type="button" className="btn" onClick={onClose}>取消</button>
          <button className="btn primary" disabled={busy || !name.trim()}>建立</button>
        </div>
      </form>
    </div>
  )
}

// ===== src/views/ProjectDetail.jsx =====



function ProjectDetail({ id }) {
  const [proj, setProj] = useState(null)
  const [stages, setStages] = useState([])
  const [assets, setAssets] = useState([])
  const [packages, setPackages] = useState([])
  const [tab, setTab] = useState('assets')
  const [showUpload, setShowUpload] = useState(null) // null | {assetId?}
  const [showPack, setShowPack] = useState(false)

  async function load() {
    const [{ data: p }, { data: st }, { data: as }, { data: pk }] = await Promise.all([
      supabase.from('projects').select('*, customers(name)').eq('id', id).single(),
      supabase.from('project_stages').select('*').eq('project_id', id).order('seq'),
      supabase.from('assets').select('*, asset_versions!assets_current_version_fk(*)').eq('project_id', id).order('created_at', { ascending: false }),
      supabase.from('delivery_packages').select('*, package_items(asset_version_id, asset_versions(*, assets(display_name)))').eq('project_id', id).order('created_at', { ascending: false }),
    ])
    setProj(p); setStages(st || []); setAssets(as || []); setPackages(pk || [])
  }
  useEffect(() => { load() }, [id])

  if (!proj) return <div className="empty">載入中…</div>

  async function setStageStatus(stage, status) {
    await supabase.from('project_stages').update({ status }).eq('id', stage.id)
    load()
  }

  async function setProjectStatus(status) {
    await supabase.from('projects').update({ status }).eq('id', id)
    await audit('update_project_status', 'project', id, { status })
    load()
  }

  return (
    <>
      <div className="page-head">
        <div>
          <div className="muted">
            <a className="plain" href="#/projects">專案</a> / {proj.code}
          </div>
          <h1 style={{ marginTop: 2 }}>{proj.name}</h1>
          <div className="meta" style={{ display: 'flex', gap: 10, marginTop: 6, fontSize: 13, color: 'var(--ink-2)' }}>
            <span className="badge" style={{ background: PROJECT_TYPES[proj.type].color }}>{PROJECT_TYPES[proj.type].label}</span>
            {proj.customers?.name && <span>{proj.customers.name}</span>}
            {proj.due_date && <span>截止 {fmtDate(proj.due_date)}</span>}
            <select value={proj.status} onChange={e => setProjectStatus(e.target.value)}
              style={{ border: '1px solid var(--line)', borderRadius: 6, padding: '2px 6px', fontSize: 12.5 }}>
              {Object.entries(PROJECT_STATUS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
            </select>
          </div>
        </div>
        <button className="btn primary" onClick={() => setShowUpload({})}>⬆ 上傳檔案</button>
      </div>

      <div className="stages">
        {stages.map(s => (
          <button key={s.id} className={`stage ${s.status}`} title="點擊切換狀態"
            onClick={() => setStageStatus(s, s.status === 'pending' ? 'active' : s.status === 'active' ? 'done' : 'pending')}>
            {s.name}
          </button>
        ))}
      </div>

      <div className="chips" style={{ marginBottom: 16 }}>
        <button className={'chip' + (tab === 'assets' ? ' on' : '')} onClick={() => setTab('assets')}>資產（{assets.length}）</button>
        <button className={'chip' + (tab === 'packages' ? ' on' : '')} onClick={() => setTab('packages')}>交付包（{packages.length}）</button>
      </div>

      {tab === 'assets' && <AssetTable assets={assets} stages={stages} onChanged={load} onNewVersion={a => setShowUpload({ assetId: a.id, name: a.display_name })} />}

      {tab === 'packages' && (
        <>
          <div className="toolbar">
            <button className="btn" onClick={() => setShowPack(true)}>＋ 建立交付包</button>
          </div>
          {packages.length === 0 ? <div className="empty">尚無交付包。把一組完稿（AI＋PDF＋輸出檔）組成一包，並指定正式版。</div> :
            packages.map(pk => <PackageCard key={pk.id} pk={pk} projectId={id} onChanged={load} />)}
        </>
      )}

      {showUpload && (
        <UploadModal projectId={id} stages={stages} preset={showUpload}
          onClose={() => setShowUpload(null)} onDone={() => { setShowUpload(null); load() }} />
      )}
      {showPack && (
        <PackageModal projectId={id} assets={assets}
          onClose={() => setShowPack(false)} onDone={() => { setShowPack(false); load() }} />
      )}
    </>
  )
}

function AssetTable({ assets, stages, onChanged, onNewVersion }) {
  if (assets.length === 0) return <div className="empty">尚無檔案，按「⬆ 上傳檔案」加入第一個資產。</div>

  async function setReview(a, review_status) {
    await supabase.from('assets').update({ review_status }).eq('id', a.id)
    await audit('set_review_status', 'asset', a.id, { review_status })
    onChanged()
  }

  async function download(a) {
    const v = a.asset_versions
    if (!v?.storage_path) return
    const url = await signedUrl(v.storage_path)
    const link = document.createElement('a')
    link.href = url; link.download = v.file_name; link.target = '_blank'
    link.click()
    await audit('download', 'asset', a.id, { version: v.version_no })
  }

  return (
    <table className="tbl">
      <thead>
        <tr><th>名稱</th><th>類型</th><th>來源</th><th>版本</th><th>審核</th><th>階段</th><th>大小</th><th>更新</th><th></th></tr>
      </thead>
      <tbody>
        {assets.map(a => {
          const v = a.asset_versions
          const rs = REVIEW_STATUS[a.review_status]
          return (
            <tr key={a.id}>
              <td>
                <strong>{a.display_name}</strong>
                {a.tags?.length > 0 && <div>{a.tags.map(t => <span key={t} className="tag">{t}</span>)}</div>}
              </td>
              <td>{VARIANTS[a.variant]}</td>
              <td className="muted">{SOURCES[a.source]}</td>
              <td>V{v?.version_no ?? '–'}</td>
              <td>
                <select value={a.review_status} onChange={e => setReview(a, e.target.value)}
                  style={{ color: rs.color, border: '1px solid var(--line)', borderRadius: 6, padding: '2px 4px', fontSize: 12.5, fontWeight: 500 }}>
                  {Object.entries(REVIEW_STATUS).map(([k, s]) => <option key={k} value={k}>{s.label}</option>)}
                </select>
              </td>
              <td className="muted">{stages.find(s => s.id === a.stage_id)?.name || '—'}</td>
              <td className="muted">{fmtSize(v?.file_size)}</td>
              <td className="muted">{fmtDate(v?.created_at || a.created_at)}</td>
              <td style={{ whiteSpace: 'nowrap' }}>
                <button className="btn small" onClick={() => download(a)}>下載</button>{' '}
                <button className="btn small" onClick={() => onNewVersion(a)}>＋新版</button>
              </td>
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}

function UploadModal({ projectId, stages, preset, onClose, onDone }) {
  const [file, setFile] = useState(null)
  const [displayName, setDisplayName] = useState(preset.name || '')
  const [stageId, setStageId] = useState('')
  const [variant, setVariant] = useState('design')
  const [source, setSource] = useState('self')
  const [note, setNote] = useState('')
  const [tags, setTags] = useState('')
  const [err, setErr] = useState('')
  const [busy, setBusy] = useState(false)
  const isNewVersion = !!preset.assetId

  async function submit(e) {
    e.preventDefault(); setBusy(true); setErr('')
    try {
      const { asset } = await uploadVersion({
        file, projectId, stageId, assetId: preset.assetId,
        displayName: displayName || file.name, source, variant, note,
      })
      if (!isNewVersion && tags.trim()) {
        await supabase.from('assets').update({
          tags: tags.split(/[,，\s]+/).filter(Boolean),
        }).eq('id', asset.id)
      }
      onDone()
    } catch (e2) { setErr(e2.message); setBusy(false) }
  }

  return (
    <div className="overlay" onClick={e => e.target === e.currentTarget && onClose()}>
      <form className="modal" onSubmit={submit}>
        <h2>{isNewVersion ? `上傳新版本 — ${preset.name}` : '上傳檔案'}</h2>
        <div className="field">
          <label>檔案（AI／KEY／SKP／PDF／圖片…）</label>
          <input type="file" onChange={e => setFile(e.target.files[0])} required />
        </div>
        {!isNewVersion && (
          <>
            <div className="field">
              <label>顯示名稱（留空用檔名）</label>
              <input value={displayName} onChange={e => setDisplayName(e.target.value)} />
            </div>
            <div className="row2">
              <div className="field">
                <label>變體類型</label>
                <select value={variant} onChange={e => setVariant(e.target.value)}>
                  {Object.entries(VARIANTS).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
                </select>
              </div>
              <div className="field">
                <label>來源</label>
                <select value={source} onChange={e => setSource(e.target.value)}>
                  {Object.entries(SOURCES).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
                </select>
              </div>
            </div>
            <div className="row2">
              <div className="field">
                <label>所屬階段（可留空）</label>
                <select value={stageId} onChange={e => setStageId(e.target.value)}>
                  <option value="">—</option>
                  {stages.map(s => <option key={s.id} value={s.id}>{s.name}</option>)}
                </select>
              </div>
              <div className="field">
                <label>標籤（逗號分隔）</label>
                <input value={tags} onChange={e => setTags(e.target.value)} placeholder="導覽牌, 完稿" />
              </div>
            </div>
          </>
        )}
        <div className="field">
          <label>版本說明</label>
          <textarea rows={2} value={note} onChange={e => setNote(e.target.value)} placeholder="這一版改了什麼？" />
        </div>
        {err && <div className="err">{err}</div>}
        <div className="actions">
          <button type="button" className="btn" onClick={onClose}>取消</button>
          <button className="btn primary" disabled={busy || !file}>{busy ? '上傳中…' : '上傳'}</button>
        </div>
      </form>
    </div>
  )
}

function PackageModal({ projectId, assets, onClose, onDone }) {
  const [name, setName] = useState(`完稿 ${new Date().toISOString().slice(0, 10)}`)
  const [note, setNote] = useState('')
  const [selected, setSelected] = useState({})
  const [err, setErr] = useState('')
  const [busy, setBusy] = useState(false)
  const candidates = assets.filter(a => a.asset_versions)

  async function submit(e) {
    e.preventDefault(); setBusy(true); setErr('')
    try {
      const { data: { user } } = await supabase.auth.getUser()
      const versionIds = Object.entries(selected).filter(([, on]) => on).map(([vid]) => vid)
      if (versionIds.length === 0) throw new Error('請至少選擇一個檔案')
      const { data: pk, error } = await supabase.from('delivery_packages').insert({
        project_id: projectId, name, note, created_by: user?.id,
      }).select().single()
      if (error) throw error
      await supabase.from('package_items').insert(versionIds.map(v => ({ package_id: pk.id, asset_version_id: v })))
      await audit('create_package', 'delivery_package', pk.id, { items: versionIds.length })
      onDone()
    } catch (e2) { setErr(e2.message); setBusy(false) }
  }

  return (
    <div className="overlay" onClick={e => e.target === e.currentTarget && onClose()}>
      <form className="modal" onSubmit={submit}>
        <h2>建立交付包</h2>
        <div className="field"><label>名稱</label><input value={name} onChange={e => setName(e.target.value)} required /></div>
        <div className="field">
          <label>選擇檔案（目前版本）</label>
          {candidates.length === 0 && <div className="muted">此專案還沒有已上傳的檔案。</div>}
          {candidates.map(a => (
            <label key={a.id} style={{ display: 'flex', gap: 8, alignItems: 'center', padding: '6px 2px', fontSize: 13.5 }}>
              <input type="checkbox" checked={!!selected[a.asset_versions.id]}
                onChange={e => setSelected({ ...selected, [a.asset_versions.id]: e.target.checked })} />
              {a.display_name} <span className="muted">V{a.asset_versions.version_no} · {a.asset_versions.file_name}</span>
            </label>
          ))}
        </div>
        <div className="field"><label>說明</label><textarea rows={2} value={note} onChange={e => setNote(e.target.value)} /></div>
        {err && <div className="err">{err}</div>}
        <div className="actions">
          <button type="button" className="btn" onClick={onClose}>取消</button>
          <button className="btn primary" disabled={busy}>建立</button>
        </div>
      </form>
    </div>
  )
}

function PackageCard({ pk, projectId, onChanged }) {
  async function makeCurrent() {
    await supabase.from('delivery_packages').update({ is_current: false }).eq('project_id', projectId).eq('is_current', true)
    await supabase.from('delivery_packages').update({ is_current: true }).eq('id', pk.id)
    await audit('set_current_package', 'delivery_package', pk.id)
    onChanged()
  }
  async function downloadItem(v) {
    const url = await signedUrl(v.storage_path)
    const link = document.createElement('a')
    link.href = url; link.download = v.file_name; link.target = '_blank'
    link.click()
  }
  return (
    <div className="card" style={{ cursor: 'default', marginBottom: 12 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <h3 style={{ margin: 0 }}>
          {pk.name}{' '}
          {pk.is_current
            ? <span className="badge" style={{ background: '#2D6A4F' }}>目前正式版</span>
            : <button className="btn small" onClick={makeCurrent}>設為正式版</button>}
        </h3>
        <span className="muted">{fmtDate(pk.created_at)}</span>
      </div>
      {pk.note && <div className="muted" style={{ margin: '6px 0' }}>{pk.note}</div>}
      <table className="tbl" style={{ marginTop: 10 }}>
        <tbody>
          {(pk.package_items || []).map(it => (
            <tr key={it.asset_version_id}>
              <td>{it.asset_versions?.assets?.display_name}</td>
              <td className="muted">V{it.asset_versions?.version_no} · {it.asset_versions?.file_name}</td>
              <td style={{ textAlign: 'right' }}>
                <button className="btn small" onClick={() => downloadItem(it.asset_versions)}>下載</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

// ===== src/views/Search.jsx =====



function Search() {
  const [q, setQ] = useState('')
  const [review, setReview] = useState('all')
  const [rows, setRows] = useState([])
  const [loading, setLoading] = useState(true)

  async function load() {
    setLoading(true)
    let query = supabase.from('assets')
      .select('*, projects(code, name), asset_versions!assets_current_version_fk(*)')
      .order('created_at', { ascending: false }).limit(200)
    if (review !== 'all') query = query.eq('review_status', review)
    const { data } = await query
    setRows(data || []); setLoading(false)
  }
  useEffect(() => { load() }, [review])

  const shown = rows.filter(a =>
    !q ||
    a.display_name.includes(q) ||
    (a.tags || []).some(t => t.includes(q)) ||
    (a.projects?.name || '').includes(q) ||
    (a.projects?.code || '').includes(q)
  )

  async function download(a) {
    const v = a.asset_versions
    if (!v?.storage_path) return
    const url = await signedUrl(v.storage_path)
    const link = document.createElement('a')
    link.href = url; link.download = v.file_name; link.target = '_blank'
    link.click()
  }

  return (
    <>
      <div className="page-head"><h1>資產搜尋</h1></div>
      <div className="toolbar">
        <input type="search" placeholder="名稱／標籤／專案" value={q} onChange={e => setQ(e.target.value)} autoFocus />
        <div className="chips">
          <button className={'chip' + (review === 'all' ? ' on' : '')} onClick={() => setReview('all')}>全部狀態</button>
          {Object.entries(REVIEW_STATUS).map(([k, s]) => (
            <button key={k} className={'chip' + (review === k ? ' on' : '')} onClick={() => setReview(k)}>{s.label}</button>
          ))}
        </div>
      </div>
      {loading ? <div className="empty">載入中…</div> :
        shown.length === 0 ? <div className="empty">沒有符合的資產。</div> : (
          <table className="tbl">
            <thead><tr><th>名稱</th><th>專案</th><th>類型</th><th>來源</th><th>版本</th><th>審核</th><th>大小</th><th>更新</th><th></th></tr></thead>
            <tbody>
              {shown.map(a => {
                const v = a.asset_versions
                const rs = REVIEW_STATUS[a.review_status]
                return (
                  <tr key={a.id}>
                    <td>
                      <strong>{a.display_name}</strong>
                      {a.tags?.length > 0 && <div>{a.tags.map(t => <span key={t} className="tag">{t}</span>)}</div>}
                    </td>
                    <td>
                      <a className="plain" href={`#/projects/${a.project_id}`}>{a.projects?.code}</a>
                      <div className="muted">{a.projects?.name}</div>
                    </td>
                    <td>{VARIANTS[a.variant]}</td>
                    <td className="muted">{SOURCES[a.source]}</td>
                    <td>V{v?.version_no ?? '–'}</td>
                    <td><span className="badge" style={{ background: rs.color }}>{rs.label}</span></td>
                    <td className="muted">{fmtSize(v?.file_size)}</td>
                    <td className="muted">{fmtDate(v?.created_at || a.created_at)}</td>
                    <td><button className="btn small" onClick={() => download(a)}>下載</button></td>
                  </tr>
                )
              })}
            </tbody>
          </table>
        )}
    </>
  )
}

// ===== src/App.jsx =====






// hash 路由：#/projects、#/projects/:id、#/search
function useRoute() {
  const [hash, setHash] = useState(window.location.hash || '#/projects')
  useEffect(() => {
    const fn = () => setHash(window.location.hash || '#/projects')
    window.addEventListener('hashchange', fn)
    return () => window.removeEventListener('hashchange', fn)
  }, [])
  return hash
}

function App() {
  const [session, setSession] = useState(undefined)
  const [profile, setProfile] = useState(null)
  const route = useRoute()

  useEffect(() => {
    supabase.auth.getSession().then(({ data }) => setSession(data.session))
    const { data: sub } = supabase.auth.onAuthStateChange((_e, s) => setSession(s))
    return () => sub.subscription.unsubscribe()
  }, [])

  useEffect(() => {
    if (!session?.user) { setProfile(null); return }
    supabase.from('profiles').select('*').eq('id', session.user.id).single()
      .then(({ data }) => setProfile(data))
  }, [session?.user?.id])

  if (session === undefined) return null
  if (!session) return <Login />

  const nav = [
    { hash: '#/projects', label: '專案' },
    { hash: '#/search', label: '資產搜尋' },
  ]

  let view = null
  const m = route.match(/^#\/projects\/([\w-]+)/)
  if (m) view = <ProjectDetail id={m[1]} />
  else if (route.startsWith('#/search')) view = <Search />
  else view = <Projects />

  return (
    <div className="shell">
      <aside className="side">
        <div className="brand">SynoFlow<small>工作室資產管理</small></div>
        {nav.map(n => (
          <button key={n.hash} className={'nav-item' + (route.startsWith(n.hash) ? ' on' : '')}
            onClick={() => (window.location.hash = n.hash)}>{n.label}</button>
        ))}
        <div className="spacer" />
        <div className="userbox">
          {profile?.display_name || session.user.email}
          {profile?.role === 'admin' && ' · 管理員'}
          <br />
          <button className="btn ghost" style={{ padding: 0, fontSize: 12 }}
            onClick={() => supabase.auth.signOut()}>登出</button>
        </div>
      </aside>
      <main className="main">{view}</main>
    </div>
  )
}

// ===== src/main.jsx =====



createRoot(document.getElementById('root')).render(<App />)
