import { ref, computed, reactive } from 'vue'
import { router } from '@inertiajs/vue3'
import axios from 'axios'
import { debounce } from 'lodash'

/**
 * Customer File Manager composable.
 *
 * Centralises state (current path, selection, clipboard, navigation history)
 * and API call helpers. Inertia is used for navigation + mutations so flash
 * messages flow through ToastNotifications.vue. Axios is used for read-only
 * JSON endpoints (tree, search, read) that should not trigger a full page
 * reload.
 *
 * Performance notes:
 *   - treeCache (Map<path, {items, expires}>) with 60s TTL avoids redundant
 *     /tree calls while sidebar/breadcrumb hover.
 *   - loadDirectory(path, force) returns cached items without round-trip,
 *     callers may then pass them to navigateTo() to update Inertia.
 *   - invalidateCache(path) is called after every mutation so the next
 *     read returns fresh data.
 *   - Optimistic delete: hides rows immediately, rolls back on failure.
 *
 * Usage:
 *   const fm = useFileManager(initialPath)
 *   fm.navigateTo('public_html/uploads')
 *   await fm.loadDirectory('public_html/uploads')   // pre-warm cache
 *   fm.copySelected()
 *   fm.paste('public_html/backup')
 */
export function useFileManager(initialPath = '') {
    // ─── Reactive state ──────────────────────────────────────────────────
    const currentPath = ref(initialPath ?? '')
    const selectedFiles = ref(new Set())
    const clipboard = reactive({
        mode: null,           // 'copy' | 'cut' | null
        paths: [],
        sourcePath: '',
    })
    const navigationHistory = reactive({
        back: [],
        forward: [],
    })

    // Tree cache (real Map for O(1) lookup, TTL-based)
    const treeCache = new Map()
    const CACHE_TTL_MS = 60_000

    // Hidden paths (optimistic delete) — UI hides them, server confirms later.
    const hiddenPaths = ref(new Set())

    const expandedPaths = ref(loadExpanded())

    // ─── Path navigation ─────────────────────────────────────────────────
    const navigateTo = (path = '', { pushHistory = true } = {}) => {
        if (pushHistory && currentPath.value !== path) {
            navigationHistory.back.push(currentPath.value)
            navigationHistory.forward.splice(0)
        }
        currentPath.value = path
        clearSelection()
        // Clear any optimistic hiding when we leave the directory.
        hiddenPaths.value = new Set()
        router.get(route('customer.files.index'), { path }, {
            preserveScroll: false,
            preserveState: false,
        })
    }

    // Debounced wrapper for navigation — prevents double clicks from spamming
    // the server when users mash a folder. 300ms is short enough to feel
    // instant but long enough to coalesce rapid clicks.
    const navigateToDebounced = debounce(navigateTo, 300, { leading: true, trailing: false })

    const navigateBack = () => {
        if (navigationHistory.back.length === 0) return
        const prev = navigationHistory.back.pop()
        navigationHistory.forward.push(currentPath.value)
        currentPath.value = prev
        router.get(route('customer.files.index'), { path: prev })
    }

    const navigateForward = () => {
        if (navigationHistory.forward.length === 0) return
        const next = navigationHistory.forward.pop()
        navigationHistory.back.push(currentPath.value)
        currentPath.value = next
        router.get(route('customer.files.index'), { path: next })
    }

    const navigateUp = () => {
        const segs = currentPath.value.split('/').filter(Boolean)
        segs.pop()
        navigateTo(segs.join('/'))
    }

    const navigateHome = () => navigateTo('')

    const reload = () => {
        router.reload({ preserveScroll: true })
    }

    // ─── Selection ───────────────────────────────────────────────────────
    const isSelected = (path) => selectedFiles.value.has(path)

    const toggleSelect = (path) => {
        const s = new Set(selectedFiles.value)
        s.has(path) ? s.delete(path) : s.add(path)
        selectedFiles.value = s
    }

    const selectFile = (path) => {
        const s = new Set(selectedFiles.value)
        s.add(path)
        selectedFiles.value = s
    }

    const deselectAll = () => {
        selectedFiles.value = new Set()
    }

    const clearSelection = deselectAll

    const selectAll = (items) => {
        selectedFiles.value = new Set(items.map(i => i.path))
    }

    const selectedCount = computed(() => selectedFiles.value.size)
    const hasSelection = computed(() => selectedFiles.value.size > 0)

    // ─── Optimistic hiding (delete) ──────────────────────────────────────
    const isHidden = (path) => hiddenPaths.value.has(path)
    const hideOptimistic = (paths) => {
        const s = new Set(hiddenPaths.value)
        for (const p of paths) s.add(p)
        hiddenPaths.value = s
    }
    const unhideOptimistic = (paths) => {
        const s = new Set(hiddenPaths.value)
        for (const p of paths) s.delete(p)
        hiddenPaths.value = s
    }

    // ─── Clipboard (copy/cut/paste) ──────────────────────────────────────
    const copySelected = () => {
        const paths = [...selectedFiles.value]
        if (paths.length === 0) return
        clipboard.mode = 'copy'
        clipboard.paths = paths
        clipboard.sourcePath = currentPath.value
        toast.info(`${paths.length} öğe kopyalandı (panoda)`)
    }

    const cutSelected = () => {
        const paths = [...selectedFiles.value]
        if (paths.length === 0) return
        clipboard.mode = 'cut'
        clipboard.paths = paths
        clipboard.sourcePath = currentPath.value
        toast.info(`${paths.length} öğe kesildi (panoda)`)
    }

    const clipboardCount = computed(() => clipboard.paths.length)
    const canPaste = computed(() => clipboard.paths.length > 0 && clipboard.mode !== null)

    /**
     * Paste clipboard contents into the target path. Iterates sequentially
     * because each Inertia post triggers a flash → toast notification.
     */
    const paste = async (targetPath = currentPath.value) => {
        if (!canPaste.value) return

        const mode = clipboard.mode
        const routeName = mode === 'cut' ? 'customer.files.move' : 'customer.files.copy'
        const pathCount = clipboard.paths.length

        for (const src of [...clipboard.paths]) {
            const name = src.split('/').pop()
            const dst = targetPath ? `${targetPath}/${name}` : name
            try {
                await axios.post(route(routeName), { src, dst })
            } catch (e) {
                toast.error(`'${name}' ${mode === 'cut' ? 'taşınamadı' : 'kopyalanamadı'}.`)
            }
        }

        if (mode === 'cut') {
            clipboard.mode = null
            clipboard.paths = []
            clipboard.sourcePath = ''
        }
        // Mutations land in target dir (and source dir on cut) — invalidate both.
        invalidateCache(targetPath)
        if (mode === 'cut') invalidateCache(clipboard.sourcePath)

        toast.success(`${pathCount} öğe ${mode === 'cut' ? 'taşındı' : 'kopyalandı'}`)
        reload()
    }

    // ─── Mutations ───────────────────────────────────────────────────────
    const deleteSelected = async () => {
        const paths = [...selectedFiles.value]
        if (paths.length === 0) return
        if (!confirm(`${paths.length} öğe çöp kutusuna taşınacak. Emin misiniz?`)) return

        // Optimistic: hide rows immediately.
        hideOptimistic(paths)
        deselectAll()

        try {
            await Promise.all(paths.map(p =>
                axios.delete(route('customer.files.destroy'), { data: { path: p, soft: true } })
            ))
            invalidateCache(currentPath.value)
            toast.success(`${paths.length} öğe çöp kutusuna taşındı.`)
            reload()
        } catch {
            // Roll back optimistic hide on failure.
            unhideOptimistic(paths)
            toast.error('Silinme sırasında hata oluştu.')
            reload()
        }
    }

    const deleteOne = async (path, soft = true) => {
        const msg = soft ? 'çöp kutusuna taşı' : 'kalıcı olarak sil'
        if (!confirm(`'${path}' ${msg}? Emin misiniz?`)) return

        hideOptimistic([path])

        try {
            await axios.delete(route('customer.files.destroy'), { data: { path, soft } })
            invalidateCache(currentPath.value)
            toast.success(`'${path.split('/').pop()}' ${soft ? 'çöp kutusuna taşındı' : 'silindi'}.`)
            reload()
        } catch (e) {
            unhideOptimistic([path])
            toast.error('Silinme sırasında hata oluştu: ' + (e?.response?.data?.error ?? e.message))
        }
    }

    // ─── Tree (lazy directory loading with TTL cache) ────────────────────
    //
    // Sidebar lazy-load: must be silent on empty/missing folders.
    // The backend returns 200 + empty items for unknown paths, so 422 only
    // surfaces for real server faults — treat those as warn, not toast (still
    // no UI noise).
    const loadDirectory = async (path = '', force = false) => {
        const key = path || '/'
        if (!force) {
            const cached = treeCache.get(key)
            if (cached && cached.expires > Date.now()) {
                return cached.items
            }
        }
        try {
            const { data } = await axios.get(route('customer.files.tree'), {
                // Send booleans as canonical "1"/"0" so Laravel's request
                // parsing stays predictable across PHP/JS coercion edges.
                params: { path, depth: 1, show_hidden: 1 },
            })
            const items = Array.isArray(data?.items) ? data.items : (Array.isArray(data) ? data : [])
            treeCache.set(key, { items, expires: Date.now() + CACHE_TTL_MS })
            return items
        } catch (e) {
            // 422 used to mean "missing folder" — now it only means real failure.
            // Keep the UI quiet either way; sidebar shouldn't shout.
            if (import.meta.env.DEV) {
                console.warn('[fm] tree load failed:', path, e?.response?.status, e?.message)
            }
            // Cache the empty result briefly so we don't hammer a 500-ing endpoint.
            treeCache.set(key, { items: [], expires: Date.now() + 5_000 })
            return []
        }
    }

    // Back-compat alias: existing callers pass loadTree to FileTree.
    const loadTree = (path = '', _depth = 1) => loadDirectory(path)

    /**
     * Invalidate cache entries that a mutation in `path` could have changed.
     * We always invalidate `path` itself plus its parent (because creating /
     * deleting a directory changes the parent's listing).
     */
    const invalidateCache = (path = null) => {
        if (path === null) {
            treeCache.clear()
            return
        }
        const key = path || '/'
        treeCache.delete(key)
        // Parent path also stale (entry list changed).
        const parent = path.split('/').slice(0, -1).join('/')
        treeCache.delete(parent || '/')
    }

    // Legacy name kept for back-compat with sidebar / other call sites.
    const invalidateTreeCache = invalidateCache

    // ─── Expanded folders memo (persisted) ───────────────────────────────
    function loadExpanded() {
        try {
            return new Set(JSON.parse(localStorage.getItem('fm_expanded') || '[]'))
        } catch {
            return new Set()
        }
    }

    const toggleExpanded = (path) => {
        const s = new Set(expandedPaths.value)
        s.has(path) ? s.delete(path) : s.add(path)
        expandedPaths.value = s
        try {
            localStorage.setItem('fm_expanded', JSON.stringify([...s]))
        } catch { /* quota exceeded — ignore */ }
    }

    const isExpanded = (path) => expandedPaths.value.has(path)

    // ─── Search (with cancel token, debounce handled by caller UI) ───────
    let searchAbort = null
    const search = async (params) => {
        // Cancel any in-flight search before issuing the new one.
        if (searchAbort) {
            try { searchAbort.abort() } catch { /* */ }
        }
        searchAbort = new AbortController()

        try {
            const { data } = await axios.get(route('customer.files.search'), {
                params: {
                    path: params.path ?? currentPath.value,
                    pattern: params.pattern ?? '',
                    content: params.content ?? '',
                    case_sensitive: params.caseSensitive ? 1 : 0,
                    max_results: params.maxResults ?? 100,
                },
                signal: searchAbort.signal,
            })
            return data
        } catch (e) {
            if (e?.code === 'ERR_CANCELED' || e?.name === 'CanceledError') {
                return null   // caller should ignore cancelled results
            }
            toast.error('Arama başarısız: ' + (e?.response?.data?.error ?? e.message))
            return { items: [], matches: [] }
        }
    }

    // ─── Read file (for editor) ──────────────────────────────────────────
    const readFile = async (path) => {
        try {
            const { data } = await axios.get(route('customer.files.read'), {
                params: { path },
            })
            return data
        } catch (e) {
            toast.error('Dosya okunamadı: ' + (e?.response?.data?.error ?? e.message))
            throw e
        }
    }

    // ─── Save file (for editor — axios so no full reload) ────────────────
    const saveFile = async (path, content) => {
        const { data } = await axios.post(route('customer.files.save'), { path, content })
        invalidateCache(path.split('/').slice(0, -1).join('/'))
        return data
    }

    // ─── Toast helper (uses global $toast injected by ToastNotifications) ─
    const toast = {
        success: (msg) => window?.$toast?.success?.(msg),
        error: (msg) => window?.$toast?.error?.(msg),
        info: (msg) => window?.$toast?.info?.(msg),
        warning: (msg) => window?.$toast?.warning?.(msg),
    }

    return {
        // state
        currentPath,
        selectedFiles,
        clipboard,
        navigationHistory,
        expandedPaths,
        hiddenPaths,
        // navigation
        navigateTo,
        navigateToDebounced,
        navigateBack,
        navigateForward,
        navigateUp,
        navigateHome,
        loadDirectory,
        reload,
        // selection
        isSelected,
        toggleSelect,
        selectFile,
        deselectAll,
        clearSelection,
        selectAll,
        selectedCount,
        hasSelection,
        // optimistic hide
        isHidden,
        hideOptimistic,
        unhideOptimistic,
        // clipboard
        copySelected,
        cutSelected,
        paste,
        clipboardCount,
        canPaste,
        // mutations
        deleteSelected,
        deleteOne,
        // tree
        loadTree,
        invalidateCache,
        invalidateTreeCache,
        toggleExpanded,
        isExpanded,
        // search
        search,
        // read/save
        readFile,
        saveFile,
        // utilities
        toast,
    }
}
