import React from 'react'
import * as tus from 'tus-js-client'

import { api, WhoAmI } from './api'
import uploaderUtils from './uploader-utils'

const {
  clampOneLevel,
  composeName,
  createNoResumeFingerprint,
  createNoResumeStorage,
  isDetailedError,
  isVideoFile,
  normalizeRows,
  sanitizeDesc,
  sanitizeFolderName,
} = uploaderUtils

type FileRow = { name: string; path: string; is_dir: boolean; size: number; mtime: number }
type Sel = { file: File; desc: string; date: string; finalName: string; progress?: number; err?: string }

type ControllerState = {
  mobile: boolean
  me: WhoAmI | undefined
  setMe: React.Dispatch<React.SetStateAction<WhoAmI | undefined>>
  libs: string[]
  lib: string
  sub: string
  rootDirs: string[]
  rows: FileRow[]
  status: string
  globalDate: string
  uploading: boolean
  sel: Sel[]
  bulkDesc: string
  folderInputRef: React.RefObject<HTMLInputElement>
  newFolderRaw: string
  setGlobalDate: React.Dispatch<React.SetStateAction<string>>
  setLib: React.Dispatch<React.SetStateAction<string>>
  setSub: React.Dispatch<React.SetStateAction<string>>
  setNewFolderRaw: React.Dispatch<React.SetStateAction<string>>
  setBulkDesc: React.Dispatch<React.SetStateAction<string>>
  setSel: React.Dispatch<React.SetStateAction<Sel[]>>
  handleChoose: (files: FileList) => void
  applyDescToAllVideos: () => void
  doUpload: () => Promise<void>
  createSubfolder: (nameRaw: string) => Promise<void>
  renameFolder: (oldName: string) => Promise<void>
  deleteFolder: (name: string) => Promise<void>
  renamePath: (oldp: string) => Promise<void>
  deletePath: (p: string, recursive: boolean) => Promise<void>
  refresh: (currLib: string, currSub: string) => Promise<void>
  sortedRows: FileRow[]
  existingNames: Set<string>
  duplicateNamesInSelection: Set<string>
  hasNameIssues: boolean
  destPath: string
  videosNeedingDesc: number
}

const NoResumeUrlStorage = createNoResumeStorage()

function useIsMobile(): boolean {
  const [mobile, setMobile] = React.useState(false)
  React.useEffect(() => {
    setMobile(uploaderUtils.isLikelyMobileUA())
  }, [])
  return mobile
}

export default function useUploaderController(): ControllerState {
  const mobile = useIsMobile()

  const [me, setMe] = React.useState<WhoAmI | undefined>()
  const [libs, setLibs] = React.useState<string[]>([])
  const [lib, setLib] = React.useState<string>('')

  const [sub, setSub] = React.useState<string>('')
  const [rootDirs, setRootDirs] = React.useState<string[]>([])
  const [rows, setRows] = React.useState<FileRow[]>([])

  const [status, setStatus] = React.useState<string>('')
  const [globalDate, setGlobalDate] = React.useState<string>(new Date().toISOString().slice(0, 10))
  const [uploading, setUploading] = React.useState<boolean>(false)
  const [sel, setSel] = React.useState<Sel[]>([])
  const [bulkDesc, setBulkDesc] = React.useState<string>('')
  const folderInputRef = React.useRef<HTMLInputElement>(null)

  const [newFolderRaw, setNewFolderRaw] = React.useState('')

  React.useEffect(() => {
    const el = folderInputRef.current
    if (!el) return
    if (mobile) {
      el.removeAttribute('webkitdirectory')
      el.removeAttribute('directory')
    } else {
      el.setAttribute('webkitdirectory', '')
      el.setAttribute('directory', '')
    }
  }, [mobile])

  React.useEffect(() => {
    ;(async () => {
      try {
        setStatus('Loading profile…')
        const m = await api<WhoAmI>('/api/whoami')
        setMe(m as any)
        const mm: any = m
        const L: string[] =
          Array.isArray(mm?.roots) ? mm.roots : Array.isArray(mm?.libs) ? mm.libs : typeof mm?.root === 'string' && mm.root ? [mm.root] : []
        setLibs(L)
        setLib('')
        setSub('')
        setStatus('Choose a library to start')
      } catch (e: any) {
        const msg = String(e?.message || e || '')
        setStatus(`Profile error: ${msg}`)
        if (msg.toLowerCase().includes('no mapping')) {
          alert('Your account is not linked to any upload library yet. Please contact the admin to be granted access.')
          try {
            await api('/api/logout', { method: 'POST' })
          } catch {
            // Best-effort logout cleanup only.
          }
          try {
            location.replace('/')
          } catch {
            // JSDOM can block navigation APIs; browsers still redirect normally.
          }
        }
      }
    })()
  }, [])

  React.useEffect(() => {
    ;(async () => {
      if (!lib) {
        setRootDirs([])
        setRows([])
        setSub('')
        return
      }
      try {
        setStatus(`Loading library “${lib}”…`)
        await refresh(lib, sub)
      } catch (e: any) {
        console.error('[Pegasus] refresh error', e)
        setStatus(`List error: ${e?.message || e}`)
      }
    })()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [lib])

  React.useEffect(() => {
    const handler = (e: BeforeUnloadEvent) => {
      if (uploading) {
        e.preventDefault()
        e.returnValue = ''
      }
    }
    window.addEventListener('beforeunload', handler)
    return () => window.removeEventListener('beforeunload', handler)
  }, [uploading])

  async function refresh(currLib: string, currSub: string) {
    if (!currLib) return
    const one = clampOneLevel(currSub)
    const listRoot = normalizeRows(await api<any[]>(`/api/list?${new URLSearchParams({ lib: currLib, path: '' })}`))
    const rootDirNames = listRoot
      .filter((e) => e.is_dir)
      .map((e) => e.name)
      .filter(Boolean)
    setRootDirs(rootDirNames.sort((a, b) => a.localeCompare(b)))
    const subOk = one && rootDirNames.includes(one) ? one : ''
    if (subOk !== currSub) setSub(subOk)

    const path = subOk ? subOk : ''
    const list = subOk ? await api<any[]>(`/api/list?${new URLSearchParams({ lib: currLib, path })}`) : listRoot
    setRows(normalizeRows(list))

    const show = `/${[currLib, path].filter(Boolean).join('/')}`
    setStatus(`Ready · Destination: ${show}`)
  }

  function handleChoose(files: FileList) {
    const arr = Array.from(files).map((f) => {
      const base = (f as any).webkitRelativePath || f.name
      const name = base.split('/').pop() || f.name
      const desc = ''
      return { file: f, desc, date: globalDate, finalName: composeName(globalDate, sanitizeDesc(desc), name), progress: 0 }
    })
    console.log('[Pegasus] selected files', arr.map((a) => a.file.name))
    setSel(arr)
  }

  React.useEffect(() => {
    setSel((old) => old.map((x) => ({ ...x, date: globalDate, finalName: composeName(globalDate, x.desc, x.file.name) })))
  }, [globalDate])

  function applyDescToAllVideos() {
    if (!bulkDesc.trim()) return
    setSel((old) =>
      old.map((x) => (isVideoFile(x.file) ? { ...x, desc: bulkDesc, finalName: composeName(x.date, bulkDesc, x.file.name) } : x))
    )
  }

  async function doUpload() {
    if (!me) {
      setStatus('Not signed in')
      return
    }
    if (!lib) {
      alert('Please select a Library to upload into.')
      return
    }
    const missingVideos = sel.filter((s) => isVideoFile(s.file) && !s.desc.trim()).length
    if (missingVideos > 0) {
      alert(`Please add a short description for all videos. ${missingVideos} video(s) missing a description.`)
      return
    }

    setStatus('Starting upload…')
    setUploading(true)
    try {
      for (const s of sel) {
        // eslint-disable-next-line no-await-in-loop
        await new Promise<void>((resolve, reject) => {
          const opts: tus.UploadOptions & { withCredentials?: boolean } = {
            endpoint: '/tus/',
            chunkSize: 5 * 1024 * 1024,
            retryDelays: [0, 1000, 3000, 5000, 10000],
            metadata: {
              filename: s.file.name,
              lib: lib,
              subdir: sub || '',
              date: s.date,
              desc: s.desc,
            },
            onError: (err: Error | tus.DetailedError) => {
              let msg = String(err)
              if (isDetailedError(err)) {
                const status = (err.originalRequest as any)?.status
                const statusText = (err.originalRequest as any)?.statusText
                if (status) msg = `HTTP ${status} ${statusText || ''}`.trim()
              }
              console.error('[Pegasus] tus error', s.file.name, err)
              setSel((old) => old.map((x) => (x.file === s.file ? { ...x, err: msg } : x)))
              setStatus(`✖ ${s.file.name}: ${msg}`)
              alert(`Upload failed: ${s.file.name}\n\n${msg}`)
              reject(err as any)
            },
            onProgress: (sent: number, total: number) => {
              const pct = Math.floor((sent / Math.max(total, 1)) * 100)
              setSel((old) => old.map((x) => (x.file === s.file ? { ...x, progress: pct } : x)))
              setStatus(`⬆ ${s.finalName}: ${pct}%`)
            },
            onSuccess: () => {
              setSel((old) => old.map((x) => (x.file === s.file ? { ...x, progress: 100, err: undefined } : x)))
              setStatus(`✔ ${s.finalName} uploaded`)
              console.log('[Pegasus] tus success', s.file.name)
              resolve()
            },
          }
          opts.withCredentials = true
          ;(opts as any).urlStorage = NoResumeUrlStorage
          ;(opts as any).fingerprint = createNoResumeFingerprint

          const up = new tus.Upload(s.file, opts)
          console.log('[Pegasus] tus.start', { name: s.file.name, size: s.file.size, type: s.file.type, lib, sub })
          up.start()
        })
      }
      setStatus('All uploads complete')
      setSel([])
      await refresh(lib, sub)
    } finally {
      setUploading(false)
    }
  }

  async function createSubfolder(nameRaw: string) {
    const name = sanitizeFolderName(nameRaw)
    if (!name) return
    try {
      await api(`/api/mkdir`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ lib: lib, path: name }),
      })
      await refresh(lib, name)
      setSub(name)
    } catch (e: any) {
      console.error('[Pegasus] mkdir error', e)
      alert(`Create folder failed:\n${e?.message || e}`)
    }
  }

  async function renameFolder(oldName: string) {
    const nn = prompt('New folder name:', oldName)
    const newName = sanitizeFolderName(nn || '')
    if (!newName || newName === oldName) return
    try {
      await api('/api/rename', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ lib: lib, from: oldName, to: newName }),
      })
      const newSub = sub === oldName ? newName : sub
      setSub(newSub)
      await refresh(lib, newSub)
    } catch (e: any) {
      console.error('[Pegasus] rename folder error', e)
      alert(`Rename failed:\n${e?.message || e}`)
    }
  }

  async function deleteFolder(name: string) {
    if (!confirm(`Delete folder “${name}” (and its contents)?`)) return
    try {
      await api(`/api/file?${new URLSearchParams({ lib: lib, path: name, recursive: 'true' })}`, { method: 'DELETE' })
      const newSub = sub === name ? '' : sub
      setSub(newSub)
      await refresh(lib, newSub)
    } catch (e: any) {
      console.error('[Pegasus] delete folder error', e)
      alert(`Delete failed:\n${e?.message || e}`)
    }
  }

  async function renamePath(oldp: string) {
    const base = oldp.split('/').pop() || ''
    const name = prompt('New name (YYYY.MM.DD.Description.OrigStem.ext):', base)
    if (!name) return
    try {
      await api('/api/rename', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ lib: lib, from: oldp, to: oldp.split('/').slice(0, -1).concat(sanitizeFolderName(name)).join('/') }),
      })
      await refresh(lib, sub)
    } catch (e: any) {
      console.error('[Pegasus] rename error', e)
      alert(`Rename failed:\n${e?.message || e}`)
    }
  }

  async function deletePath(p: string, recursive: boolean) {
    if (!confirm(`Delete ${p}${recursive ? ' (recursive)' : ''}?`)) return
    try {
      await api(`/api/file?${new URLSearchParams({ lib: lib, path: p, recursive: recursive ? 'true' : 'false' })}`, {
        method: 'DELETE',
      })
      await refresh(lib, sub)
    } catch (e: any) {
      console.error('[Pegasus] delete error', e)
      alert(`Delete failed:\n${e?.message || e}`)
    }
  }

  const sortedRows = React.useMemo(() => {
    const arr = Array.isArray(rows) ? rows.slice() : []
    return arr.sort((a, b) => {
      const dirFirst = Number(b?.is_dir ? 1 : 0) - Number(a?.is_dir ? 1 : 0)
      if (dirFirst !== 0) return dirFirst
      const an = a?.name ?? ''
      const bn = b?.name ?? ''
      return an.localeCompare(bn)
    })
  }, [rows])

  const existingNames = React.useMemo(() => new Set(sortedRows.filter((r) => !r.is_dir).map((r) => r.name)), [sortedRows])

  const duplicateNamesInSelection = React.useMemo(() => {
    const counts = new Map<string, number>()
    sel.forEach((s) => counts.set(s.finalName, (counts.get(s.finalName) || 0) + 1))
    return new Set(Array.from(counts.entries()).filter(([, c]) => c > 1).map(([n]) => n))
  }, [sel])

  const hasNameIssues = React.useMemo(
    () => sel.some((s) => duplicateNamesInSelection.has(s.finalName) || existingNames.has(s.finalName)),
    [sel, duplicateNamesInSelection, existingNames]
  )

  const destPath = `/${[lib, sub].filter(Boolean).join('/')}`
  const videosNeedingDesc = sel.filter((s) => isVideoFile(s.file) && !s.desc.trim()).length

  return {
    mobile,
    me,
    setMe,
    libs,
    lib,
    sub,
    rootDirs,
    rows,
    status,
    globalDate,
    uploading,
    sel,
    bulkDesc,
    folderInputRef,
    newFolderRaw,
    setGlobalDate,
    setLib,
    setSub,
    setNewFolderRaw,
    setBulkDesc,
    setSel,
    handleChoose,
    applyDescToAllVideos,
    doUpload,
    createSubfolder,
    renameFolder,
    deleteFolder,
    renamePath,
    deletePath,
    refresh,
    sortedRows,
    existingNames,
    duplicateNamesInSelection,
    hasNameIssues,
    destPath,
    videosNeedingDesc,
  }
}
