import { useCallback, useEffect, useRef, useState } from 'react'
import { api, sha256Hex, uploadFile } from '../api'
import { fmtBytes } from './SelectionBar'

const UPLOAD_CONCURRENCY = 3

function fmtEta(seconds) {
  if (!isFinite(seconds) || seconds < 0) return ''
  if (seconds < 60) return `${Math.ceil(seconds)}s`
  if (seconds < 3600) return `${Math.ceil(seconds / 60)} min`
  return `${Math.floor(seconds / 3600)}h ${Math.ceil((seconds % 3600) / 60)} min`
}

export default function UploadZone({ albumId, onUploaded, processing, onRetry, onDelete }) {
  const [queue, setQueue] = useState([])
  const [dragging, setDragging] = useState(false)
  const [speed, setSpeed] = useState(0)
  const inputRef = useRef(null)
  const running = useRef(0)
  const pending = useRef([])
  const lastRefresh = useRef(0)
  const loadedRef = useRef(0)

  const totalBytes = queue.reduce((sum, item) => sum + item.file.size, 0)
  const loadedBytes = queue.reduce(
    (sum, item) =>
      sum + (item.status === 'done' ? item.file.size : (item.progress || 0) * item.file.size),
    0,
  )
  loadedRef.current = loadedBytes
  const active = queue.some((item) => ['uploading', 'queued', 'checking'].includes(item.status))

  // Sample throughput once a second (EMA-smoothed) while uploads run.
  useEffect(() => {
    if (!active) {
      setSpeed(0)
      return
    }
    let last = { loaded: loadedRef.current, time: Date.now() }
    const timer = setInterval(() => {
      const now = Date.now()
      const instant = (loadedRef.current - last.loaded) / ((now - last.time) / 1000)
      last = { loaded: loadedRef.current, time: now }
      setSpeed((prev) => (prev > 0 ? prev * 0.7 + instant * 0.3 : instant))
    }, 1000)
    return () => clearInterval(timer)
  }, [active])

  // Refresh the album at most every 5s during a bulk upload (the processing
  // poll keeps it fresh anyway), plus once when the queue drains.
  const refresh = useCallback(() => {
    const drained = running.current === 0 && pending.current.length === 0
    if (drained || Date.now() - lastRefresh.current > 5000) {
      lastRefresh.current = Date.now()
      onUploaded()
    }
  }, [onUploaded])

  const pump = useCallback(() => {
    while (running.current < UPLOAD_CONCURRENCY && pending.current.length > 0) {
      const item = pending.current.shift()
      running.current += 1
      const update = (patch) =>
        setQueue((q) => q.map((x) => (x.key === item.key ? { ...x, ...patch } : x)))
      const transfer = async () => {
        // Hash locally first: content the album already has is skipped
        // without transferring a single byte.
        update({ status: 'checking' })
        try {
          const hash = await sha256Hex(item.file)
          await api(`/api/albums/${albumId}/photos/by-hash/${hash}`)
          update({ status: 'skipped', progress: 1 })
          return
        } catch {
          // 404 (not there yet) or hashing unavailable — upload normally.
        }
        update({ status: 'uploading' })
        const url = `/api/albums/${albumId}/photos?filename=${encodeURIComponent(item.file.name)}`
        await uploadFile(url, item.file, (p) => update({ progress: p }))
        update({ status: 'done', progress: 1 })
      }
      transfer()
        .catch((e) => update({ status: 'error', error: e.message }))
        .finally(() => {
          running.current -= 1
          refresh()
          pump()
        })
    }
  }, [albumId, refresh])

  const addFiles = useCallback(
    (files) => {
      const items = [...files].map((file, i) => ({
        key: `${Date.now()}-${i}-${file.name}`,
        file,
        status: 'queued',
        progress: 0,
      }))
      if (items.length === 0) return
      setQueue((q) => [...q.filter((x) => x.status !== 'done'), ...items])
      pending.current.push(...items)
      pump()
    },
    [pump],
  )

  // The whole window is the drop target; a fullscreen overlay signals it
  // while a file drag is over the page. Depth counter because dragenter/
  // dragleave fire for every crossed element.
  useEffect(() => {
    let depth = 0
    const hasFiles = (e) => [...(e.dataTransfer?.types || [])].includes('Files')
    const enter = (e) => {
      if (!hasFiles(e)) return
      e.preventDefault()
      depth += 1
      setDragging(true)
    }
    const over = (e) => {
      if (hasFiles(e)) e.preventDefault()
    }
    const leave = (e) => {
      if (!hasFiles(e)) return
      depth -= 1
      if (depth <= 0) {
        depth = 0
        setDragging(false)
      }
    }
    const drop = (e) => {
      if (!hasFiles(e)) return
      e.preventDefault()
      depth = 0
      setDragging(false)
      addFiles(e.dataTransfer.files)
    }
    window.addEventListener('dragenter', enter)
    window.addEventListener('dragover', over)
    window.addEventListener('dragleave', leave)
    window.addEventListener('drop', drop)
    return () => {
      window.removeEventListener('dragenter', enter)
      window.removeEventListener('dragover', over)
      window.removeEventListener('dragleave', leave)
      window.removeEventListener('drop', drop)
    }
  }, [addFiles])

  return (
    <>
      <button
        className="btn btn-primary"
        title="Or drop RAWs/JPGs anywhere on the page"
        onClick={() => inputRef.current?.click()}
      >
        Upload
      </button>
      <input
        ref={inputRef}
        type="file"
        multiple
        hidden
        onChange={(e) => {
          addFiles(e.target.files)
          e.target.value = ''
        }}
      />
      {dragging && <div className="drop-overlay">Drop to upload</div>}
      <ActivityOverlay
        queue={queue}
        active={active}
        speed={speed}
        loadedBytes={loadedBytes}
        totalBytes={totalBytes}
        processing={processing}
        onRetry={onRetry}
        onDelete={onDelete}
        onClearDone={() =>
          setQueue((q) => q.filter((i) => i.status !== 'done' && i.status !== 'skipped'))
        }
      />
    </>
  )
}

// Bottom-right collapsible activity panel: upload queue and the worker's
// processing queue in one place, out of the page flow.
function ActivityOverlay({
  queue,
  active,
  speed,
  loadedBytes,
  totalBytes,
  processing,
  onRetry,
  onDelete,
  onClearDone,
}) {
  const [collapsed, setCollapsed] = useState(false)
  if (queue.length === 0 && processing.length === 0) return null

  const finished = queue.filter((i) => i.status === 'done' || i.status === 'skipped').length
  const uploadErrors = queue.filter((i) => i.status === 'error').length
  const working = processing.filter((p) => p.status !== 'error')
  const failed = processing.filter((p) => p.status === 'error')
  const summary = [
    queue.length > 0 &&
      (active
        ? `Uploading ${finished}/${queue.length}`
        : uploadErrors > 0
          ? `${uploadErrors} upload${uploadErrors === 1 ? '' : 's'} failed`
          : 'Uploads done'),
    working.length > 0 && `${working.length} processing`,
  ].filter(Boolean)

  return (
    <div className="activity">
      <button className="activity-head" onClick={() => setCollapsed((c) => !c)}>
        <span className="upload-name">
          {summary.join(' · ')}
          {failed.length > 0 && (
            <span className="error">
              {summary.length > 0 && ' · '}
              {failed.length} failed
            </span>
          )}
        </span>
        <span className="row">
          {!active && finished > 0 && (
            <span
              className="activity-clear"
              title="Clear finished uploads"
              onClick={(e) => {
                e.stopPropagation()
                onClearDone()
              }}
            >
              ✕
            </span>
          )}
          {collapsed ? '▸' : '▾'}
        </span>
      </button>
      {active && <progress className="activity-total" value={loadedBytes} max={totalBytes || 1} />}
      {!collapsed && (
        <div className="activity-body">
          {queue.length > 0 && (
            <>
              <div className="muted activity-section">
                {fmtBytes(loadedBytes)} of {fmtBytes(totalBytes)}
                {active && speed > 0 && (
                  <>
                    {' · '}
                    {fmtBytes(speed)}/s · ~{fmtEta((totalBytes - loadedBytes) / speed)} left
                  </>
                )}
              </div>
              <ul className="upload-list">
                {queue.map((item) => (
                  <li key={item.key} className={`upload-item ${item.status}`}>
                    <span className="upload-name">{item.file.name}</span>
                    {item.status === 'error' ? (
                      <span className="error">{item.error}</span>
                    ) : item.status === 'skipped' ? (
                      <span className="muted">already uploaded</span>
                    ) : item.status === 'checking' ? (
                      <span className="muted">checking…</span>
                    ) : (
                      <progress value={item.progress} max="1" />
                    )}
                  </li>
                ))}
              </ul>
            </>
          )}
          {processing.length > 0 && (
            <>
              <div className="muted activity-section">Processing</div>
              <ul className="pending-list">
                {processing.map((p) => (
                  <li key={p.id}>
                    <span className="upload-name">{p.filename}</span>
                    {p.status === 'error' ? (
                      <span className="row">
                        <span className="error">{p.error || 'failed'}</span>
                        <button className="btn" onClick={() => onRetry(p)}>
                          Retry
                        </button>
                        <button className="btn btn-danger" onClick={() => onDelete(p.id)}>
                          Delete
                        </button>
                      </span>
                    ) : (
                      <span className="muted">{p.status}…</span>
                    )}
                  </li>
                ))}
              </ul>
            </>
          )}
        </div>
      )}
    </div>
  )
}
