<script lang="ts">
import { onMount, untrack } from 'svelte';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import { goto } from '$app/navigation';
import {
    ArrowUpDown,
    ChevronDown,
    ChevronLeft,
    ChevronRight,
    ChevronUp,
    ClipboardCopy,
    Download,
    File,
    FileX,
    Funnel,
    Link,
    LoaderCircle,
    RefreshCw,
    Search,
    Trash2,
    Unlink,
    X
} from '@lucide/svelte';

import { api } from '$lib/ae_api/api';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { load_ae_obj_li__account } from '$lib/ae_core/ae_core__account';
import { delete_ae_obj_id__hosted_file, download_ae_obj_id__hosted_file } from '$lib/ae_core/core__hosted_files';
import type { ae_HostedFile } from '$lib/types/ae_types';

// ── Account map ───────────────────────────────────────────────────────────────
// Loaded with enabled:'all' so disabled client accounts appear in the dropdown.
// This is intentional — the primary use case for this page is cleaning up files
// that belong to old/inactive accounts.
let account_li: any[] = $state([]);
let account_map = new SvelteMap<string, string>();

onMount(async () => {
    if (!$ae_loc.manager_access) {
        goto('/core');
        return;
    }
    const accts = await load_ae_obj_li__account({
        api_cfg: $ae_api,
        enabled: 'all',
        hidden: 'all',
        try_cache: false,
        log_lvl: 0
    });
    account_li = accts ?? [];
    account_map.clear();
    for (const a of account_li) {
        const label = a.code ?? a.name ?? a.account_id;
        account_map.set(a.account_id, a.enable ? label : `${label} (disabled)`);
    }
});

// ── Filter state ──────────────────────────────────────────────────────────────
let qry_filename = $state('');
let qry_account_id = $state('');
let qry_for_type = $state('');
let qry_group = $state('');
let qry_enabled = $state<'all' | 'enabled' | 'not_enabled'>('all');
let qry_hidden = $state<'all' | 'not_hidden' | 'hidden'>('all');
let page_limit = $state(50);
let page_offset = $state(0);
let sort_col = $state('updated_on');
let sort_dir = $state<'ASC' | 'DESC'>('DESC');

// ── Results ───────────────────────────────────────────────────────────────────
let results: ae_HostedFile[] = $state([]);
let loading = $state(false);
let searched = $state(false);
let deleting_id = $state<string | null>(null);

// ── Multi-select ──────────────────────────────────────────────────────────────
let selected_ids = new SvelteSet<string>();
let bulk_deleting = $state(false);
let bulk_delete_progress = $state<{ done: number; total: number } | null>(null);
// When true, single-file deletes skip the confirm dialog.
let skip_confirm = $state(false);

// Auto-load on first render once the API config is ready
$effect(() => {
    if (!$ae_api?.base_url || searched || loading) return;
    untrack(() => do_search());
});

// ── Search ────────────────────────────────────────────────────────────────────
async function do_search(reset = true) {
    if (reset) page_offset = 0;
    loading = true;

    const search_query: any = { and: [] };

    if (qry_filename.trim()) {
        search_query.and.push({ field: 'filename', op: 'like', value: qry_filename.trim() });
    }
    if (qry_account_id.trim()) {
        // account_id_random matches the V3 search field convention (same as data_stores)
        search_query.and.push({ field: 'account_id_random', op: 'eq', value: qry_account_id.trim() });
    }
    if (qry_for_type.trim()) {
        search_query.and.push({ field: 'for_type', op: 'like', value: qry_for_type.trim() });
    }
    if (qry_group.trim()) {
        search_query.and.push({ field: 'group', op: 'like', value: qry_group.trim() });
    }

    const result_li = await api.search_ae_obj({
        api_cfg: $ae_api,
        obj_type: 'hosted_file',
        search_query,
        enabled: qry_enabled,
        hidden: qry_hidden,
        order_by_li: { [sort_col]: sort_dir },
        limit: page_limit,
        offset: page_offset,
        log_lvl: 0
    });

    results = result_li ?? [];
    selected_ids.clear();
    searched = true;
    loading = false;
}

function clear_filters() {
    qry_filename = '';
    qry_account_id = '';
    qry_for_type = '';
    qry_group = '';
    qry_enabled = 'all';
    qry_hidden = 'all';
    results = [];
    searched = false;
}

function toggle_sort(col: string) {
    if (sort_col === col) {
        sort_dir = sort_dir === 'ASC' ? 'DESC' : 'ASC';
    } else {
        sort_col = col;
        sort_dir = 'ASC';
    }
    do_search(false);
}

// ── Delete ────────────────────────────────────────────────────────────────────
async function handle_delete(file: ae_HostedFile) {
    const id = file.hosted_file_id;
    const label = (file as any).filename_w_ext ?? file.filename ?? id;

    // Load links if not already fetched — needed to pass correct link_to_type/link_to_id_random
    // and to warn the user how many links will be removed.
    if (!links_map.has(id)) {
        links_loading.set(id, true);
        try {
            const result = await api.get_object({ api_cfg: $ae_api, endpoint: `/v3/action/hosted_file/${id}/links`, log_lvl: 0 });
            links_map.set(id, result?.data ?? result ?? []);
        } catch (e) {
            links_map.set(id, []);
        } finally {
            links_loading.delete(id);
        }
    }

    const file_links = links_map.get(id) ?? [];
    const link_count = file_links.length;
    const link_summary = link_count > 0
        ? `\n\nThis file has ${link_count} link${link_count !== 1 ? 's' : ''} (${file_links.map((l) => l.link_to_type).join(', ')}) — all will be removed.`
        : '\n\nThis file has no links and will be deleted immediately.';

    if (!skip_confirm && !confirm(`Delete "${label}"?${link_summary}\n\nThe physical file will be removed from disk. This cannot be undone.`))
        return;

    deleting_id = id;
    try {
        // Remove each link in sequence, then hard-delete on the final call.
        // The backend only cleans up the physical file + record when rm_orphan=true
        // and no links remain, so we must delete all links first.
        if (file_links.length > 0) {
            for (let i = 0; i < file_links.length; i++) {
                const lnk = file_links[i];
                const is_last = i === file_links.length - 1;
                await api.delete_hosted_file({
                    api_cfg: $ae_api,
                    hosted_file_id: id,
                    link_to_type: lnk.link_to_type,
                    link_to_id: lnk.link_to_id_random ?? undefined,
                    rm_orphan: is_last,
                    params: { method: 'delete' },
                    log_lvl: 1
                });
            }
        } else {
            // Already an orphan — just clean it up directly.
            await api.delete_hosted_file({
                api_cfg: $ae_api,
                hosted_file_id: id,
                rm_orphan: true,
                params: { method: 'delete' },
                log_lvl: 1
            });
        }

        links_map.delete(id);
        results = results.filter((r) => r.hosted_file_id !== id);
    } catch (e) {
        console.error('[delete hosted_file]', e);
        alert('Delete failed — check console for details.');
    } finally {
        deleting_id = null;
    }
}

// ── Download ──────────────────────────────────────────────────────────────────
let downloading_id = $state<string | null>(null);

async function handle_download(file: ae_HostedFile) {
    downloading_id = file.hosted_file_id;
    try {
        await download_ae_obj_id__hosted_file({
            api_cfg: $ae_api,
            hosted_file_id: file.hosted_file_id,
            filename: (file as any).filename_w_ext ?? file.filename,
            auto_download: true,
            log_lvl: 1
        });
    } catch (e) {
        console.error('[download hosted_file]', e);
        alert('Download failed — check console for details.');
    } finally {
        downloading_id = null;
    }
}

async function copy_hash(hash: string) {
    await navigator.clipboard.writeText(hash);
}

// ── Links (lazy per row) ──────────────────────────────────────────────────────
// Keyed by hosted_file_id. Value: null = not loaded, [] = loaded/empty, [...] = loaded with links.
let links_map = new SvelteMap<string, { link_to_type: string; link_to_id: number; link_to_id_random: string | null }[] | null>();
let links_loading = new SvelteMap<string, boolean>();
// Resolved navigation URLs per link — keyed as `${link_to_type}:${link_to_id_random}`.
// Null means no routable page exists for that type.
let link_url_map = new SvelteMap<string, string | null>();

async function toggle_links(file: ae_HostedFile) {
    const id = file.hosted_file_id;
    if (links_map.has(id)) {
        links_map.delete(id);
        return;
    }
    links_loading.set(id, true);
    try {
        const result = await api.get_object({
            api_cfg: $ae_api,
            endpoint: `/v3/action/hosted_file/${id}/links`,
            log_lvl: 0
        });
        const file_links = result?.data ?? result ?? [];
        links_map.set(id, file_links);
        // Resolve navigation URLs for each link in parallel.
        await Promise.all(file_links.map((lnk: any) => resolve_link_url(lnk.link_to_type, lnk.link_to_id_random)));
    } catch (e) {
        console.error('[hosted_file links]', e);
        links_map.set(id, []);
    } finally {
        links_loading.delete(id);
    }
}

// Resolves a link record to a navigable app URL. Caches results to avoid repeat calls.
// For nested types (session, presenter, location, journal_entry, etc.) we fetch the
// object to get its parent ID, then construct the correct route.
async function resolve_link_url(link_to_type: string, id_random: string | null): Promise<void> {
    if (!id_random) return;
    const cache_key = `${link_to_type}:${id_random}`;
    if (link_url_map.has(cache_key)) return;

    // Direct types — no parent lookup needed.
    const direct: Record<string, string> = {
        event: `/events/${id_random}`,
        journal: `/journals/${id_random}`,
        archive: `/idaa/archives/${id_random}`,
        post: `/idaa/bb/${id_random}`
    };
    if (direct[link_to_type]) {
        link_url_map.set(cache_key, direct[link_to_type]);
        return;
    }

    // Nested types — look up parent ID from the object, then build URL.
    try {
        const result = await api.get_object({
            api_cfg: $ae_api,
            endpoint: `/v3/crud/${link_to_type}/${id_random}`,
            log_lvl: 0
        });
        const obj = result?.data ?? result;
        let url: string | null = null;

        if (link_to_type === 'event_session' && obj?.event_id) {
            url = `/events/${obj.event_id}/session/${id_random}`;
        } else if (link_to_type === 'event_location' && obj?.event_id) {
            url = `/events/${obj.event_id}/location/${id_random}`;
        } else if (link_to_type === 'event_presenter' && obj?.event_id) {
            url = `/events/${obj.event_id}/presenter/${id_random}`;
        } else if (link_to_type === 'journal_entry' && obj?.journal_id) {
            url = `/journals/${obj.journal_id}/entry/${id_random}`;
        } else if (link_to_type === 'archive_content' && obj?.archive_id) {
            // No dedicated content detail page — link to the parent archive.
            url = `/idaa/archives/${obj.archive_id}`;
        } else if (link_to_type === 'post_comment' && obj?.post_id) {
            // No dedicated comment detail page — link to the parent post.
            url = `/idaa/bb/${obj.post_id}`;
        }

        link_url_map.set(cache_key, url);
    } catch {
        link_url_map.set(cache_key, null);
    }
}

// ── Helpers ───────────────────────────────────────────────────────────────────
function fmt_size(bytes: number | undefined): string {
    if (!bytes) return '—';
    if (bytes < 1024) return `${bytes} B`;
    if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
    if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1_048_576).toFixed(1)} MB`;
    return `${(bytes / 1_073_741_824).toFixed(2)} GB`;
}

function fmt_account(account_id: string | null | undefined): string {
    if (!account_id) return '—';
    return account_map.get(account_id) ?? account_id.slice(0, 8) + '…';
}

function fmt_date(val: string | Date | null | undefined) {
    if (!val) return '—';
    return ae_util.iso_datetime_formatter(val as any, 'datetime_12_short');
}

// ── Orphan check ──────────────────────────────────────────────────────────────
// A file is a confirmed orphan once links_map has been populated for it AND the
// links array is empty. links_map is a SvelteMap so this derived stays reactive.
let orphan_checking = $state(false);
let orphan_filter = $state(false);

let orphan_ids = $derived(
    new Set(
        results
            .filter((f) => links_map.has(f.hosted_file_id) && (links_map.get(f.hosted_file_id)?.length ?? 1) === 0)
            .map((f) => f.hosted_file_id)
    )
);

let displayed_results = $derived(
    orphan_filter ? results.filter((f) => orphan_ids.has(f.hosted_file_id)) : results
);

let total_size = $derived(displayed_results.reduce((sum, f) => sum + (f.size ?? 0), 0));

async function check_all_for_orphans() {
    orphan_checking = true;
    orphan_filter = false;
    try {
        // Single backend query instead of N+1 per-file link fetches.
        const result = await api.get_object({
            api_cfg: $ae_api,
            endpoint: `/v3/action/hosted_file/orphan_scan?limit=500`,
            log_lvl: 0
        });
        const db_orphans: { hosted_file_id: string }[] = result?.db_orphans ?? result?.data?.db_orphans ?? [];
        const orphan_id_set = new Set(db_orphans.map((o) => o.hosted_file_id));
        // Only mark confirmed orphans — empty array means no links found.
        // Non-orphans are left out of links_map entirely so the link toggle
        // still works normally and orphan_ids only captures confirmed orphans.
        for (const file of results) {
            if (orphan_id_set.has(file.hosted_file_id)) {
                links_map.set(file.hosted_file_id, []);
            }
        }
    } catch (e) {
        console.error('[orphan_scan]', e);
    } finally {
        orphan_filter = true;
        orphan_checking = false;
    }
}

// ── Multi-select logic ────────────────────────────────────────────────────────
// Must be declared after displayed_results (which depends on orphan_filter/results).
let all_visible_selected = $derived(
    displayed_results.length > 0 &&
    displayed_results.every(f => selected_ids.has(f.hosted_file_id))
);

function toggle_select(id: string) {
    if (selected_ids.has(id)) selected_ids.delete(id);
    else selected_ids.add(id);
}

function toggle_select_all() {
    if (all_visible_selected) {
        for (const f of displayed_results) selected_ids.delete(f.hosted_file_id);
    } else {
        for (const f of displayed_results) selected_ids.add(f.hosted_file_id);
    }
}

async function handle_bulk_delete() {
    const ids_to_delete = displayed_results
        .filter(f => selected_ids.has(f.hosted_file_id))
        .map(f => f.hosted_file_id);
    if (ids_to_delete.length === 0) return;

    if (!confirm(`Delete ${ids_to_delete.length} file${ids_to_delete.length !== 1 ? 's' : ''}?\n\nPhysical files will be removed from disk. This cannot be undone.`))
        return;

    bulk_deleting = true;
    bulk_delete_progress = { done: 0, total: ids_to_delete.length };

    for (const id of ids_to_delete) {
        try {
            if (!links_map.has(id)) {
                try {
                    const result = await api.get_object({ api_cfg: $ae_api, endpoint: `/v3/action/hosted_file/${id}/links`, log_lvl: 0 });
                    links_map.set(id, result?.data ?? result ?? []);
                } catch {
                    links_map.set(id, []);
                }
            }
            const file_links = links_map.get(id) ?? [];
            if (file_links.length > 0) {
                for (let i = 0; i < file_links.length; i++) {
                    const lnk = file_links[i];
                    await api.delete_hosted_file({
                        api_cfg: $ae_api,
                        hosted_file_id: id,
                        link_to_type: lnk.link_to_type,
                        link_to_id: lnk.link_to_id_random ?? undefined,
                        rm_orphan: i === file_links.length - 1,
                        params: { method: 'delete' },
                        log_lvl: 0
                    });
                }
            } else {
                await api.delete_hosted_file({
                    api_cfg: $ae_api,
                    hosted_file_id: id,
                    rm_orphan: true,
                    params: { method: 'delete' },
                    log_lvl: 0
                });
            }
            links_map.delete(id);
            selected_ids.delete(id);
            results = results.filter(r => r.hosted_file_id !== id);
        } catch (e) {
            console.error(`[bulk delete] ${id}`, e);
        }
        bulk_delete_progress = { done: (bulk_delete_progress?.done ?? 0) + 1, total: ids_to_delete.length };
    }

    bulk_deleting = false;
    bulk_delete_progress = null;
}
</script>

<div class="space-y-6">

    <!-- ── Header ──────────────────────────────────────────────────────────── -->
    <header class="bg-surface-100-900 border-surface-500/10 flex flex-wrap items-center justify-between gap-4 rounded-xl border p-4 shadow-lg">
        <div class="flex items-center gap-3">
            <div class="bg-primary-500/10 rounded-lg p-2">
                <File size={24} class="text-primary-500" />
            </div>
            <div>
                <h1 class="h2 font-black tracking-tight">Hosted Files</h1>
                <p class="text-xs font-bold tracking-widest uppercase opacity-50">File Storage Management</p>
            </div>
        </div>
    </header>

    <!-- ── Filter bar ──────────────────────────────────────────────────────── -->
    <div class="card preset-tonal-surface border-surface-500/10 space-y-3 border p-4 shadow-xl">
        <div class="flex items-center gap-2 text-[10px] font-bold tracking-widest uppercase opacity-50">
            <Funnel size={11} /> Filters
        </div>

        <div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">
                    Filename <span class="font-normal opacity-60">— use <code>%</code> as wildcard</span>
                </span>
                <input
                    type="search"
                    bind:value={qry_filename}
                    placeholder="report%.pdf"
                    class="input input-sm w-full font-mono text-xs"
                    onkeydown={(e) => e.key === 'Enter' && do_search()} />
            </label>

            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">Account</span>
                <select class="select select-sm w-full text-xs" bind:value={qry_account_id}>
                    <option value="">All accounts</option>
                    {#each account_li as acct (acct.account_id)}
                        <option value={acct.account_id}>
                            {acct.code ?? acct.name ?? acct.account_id}
                            {acct.enable ? '' : ' (disabled)'}
                        </option>
                    {/each}
                </select>
            </label>

            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">For Type</span>
                <input
                    type="search"
                    bind:value={qry_for_type}
                    placeholder="event, person…"
                    class="input input-sm w-full font-mono text-xs"
                    onkeydown={(e) => e.key === 'Enter' && do_search()} />
            </label>

            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">Group</span>
                <input
                    type="search"
                    bind:value={qry_group}
                    placeholder="file group…"
                    class="input input-sm w-full font-mono text-xs"
                    onkeydown={(e) => e.key === 'Enter' && do_search()} />
            </label>
        </div>

        <div class="flex flex-wrap items-center gap-3 pt-1">
            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">Status</span>
                <select class="select select-sm text-xs" bind:value={qry_enabled}>
                    <option value="all">All</option>
                    <option value="enabled">Enabled</option>
                    <option value="not_enabled">Disabled</option>
                </select>
            </label>
            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">Hidden</span>
                <select class="select select-sm text-xs" bind:value={qry_hidden}>
                    <option value="all">All</option>
                    <option value="not_hidden">Visible only</option>
                    <option value="hidden">Hidden only</option>
                </select>
            </label>
            <label class="space-y-1">
                <span class="text-xs font-bold opacity-70">Per page</span>
                <select class="select select-sm text-xs" bind:value={page_limit}>
                    <option value={25}>25</option>
                    <option value={50}>50</option>
                    <option value={100}>100</option>
                    <option value={200}>200</option>
                    <option value={400}>400</option>
                    <option value={800}>800</option>
                    <option value={1000}>1000</option>
                    <option value={1500}>1500</option>
                    <option value={2000}>2000</option>
                </select>
            </label>
            <div class="flex flex-1 items-end justify-end gap-2">
                <button
                    type="button"
                    onclick={clear_filters}
                    class="btn btn-sm preset-tonal-surface text-xs"
                    title="Clear all filters">
                    <X size={13} class="mr-1" /> Clear
                </button>
                <button
                    type="button"
                    onclick={() => do_search()}
                    class="btn preset-filled-primary"
                    disabled={loading}>
                    {#if loading}
                        <LoaderCircle size={16} class="mr-2 animate-spin" />
                    {:else}
                        <Search size={16} class="mr-2" />
                    {/if}
                    Search
                </button>
            </div>
        </div>
    </div>

    <!-- ── Results ───────────────────────────────────────────────────────────── -->
    {#if results.length > 0}
        <div class="card preset-tonal-surface border-surface-500/10 border shadow-xl">

            <div class="border-surface-500/20 flex flex-wrap items-center justify-between gap-2 border-b px-4 py-2">
                <span class="text-xs font-bold opacity-60">
                    {#if orphan_filter}
                        <span class="text-warning-600 dark:text-warning-400">
                            {orphan_ids.size} orphan{orphan_ids.size !== 1 ? 's' : ''} of {results.length}
                        </span>
                    {:else}
                        {results.length} file{results.length !== 1 ? 's' : ''}
                    {/if}
                    {#if results.length === page_limit}
                        <span class="opacity-50"> (may be more)</span>
                    {/if}
                    · <span class="tabular-nums">{fmt_size(total_size)}</span> total
                </span>
                <div class="flex flex-wrap items-center gap-2">
                    <!-- Quick delete toggle — skips the per-file confirm dialog for single deletes -->
                    <label class="flex cursor-pointer items-center gap-1.5 text-xs opacity-70 hover:opacity-100"
                        title="Quick delete: skip the confirm dialog when deleting individual files">
                        <input type="checkbox" class="checkbox checkbox-sm" bind:checked={skip_confirm} />
                        Quick delete
                    </label>
                    <!-- Orphan check -->
                    {#if orphan_filter}
                        <button
                            type="button"
                            class="btn btn-sm preset-tonal-warning text-xs"
                            onclick={() => { orphan_filter = false; }}
                            title="Show all files">
                            <Unlink size={12} class="mr-1" />
                            {orphan_ids.size} orphan{orphan_ids.size !== 1 ? 's' : ''} — Show All
                        </button>
                    {:else}
                        <button
                            type="button"
                            class="btn btn-sm preset-tonal-surface text-xs"
                            onclick={check_all_for_orphans}
                            disabled={orphan_checking}
                            title="Check all visible files for missing links (orphan detection)">
                            {#if orphan_checking}
                                <LoaderCircle size={12} class="mr-1 animate-spin" /> Checking…
                            {:else}
                                <Unlink size={12} class="mr-1" /> Check Orphans
                            {/if}
                        </button>
                    {/if}
                    {#if page_offset > 0}
                        <button
                            type="button"
                            class="btn btn-sm preset-tonal-surface"
                            onclick={() => { page_offset = Math.max(0, page_offset - page_limit); do_search(false); }}>
                            <ChevronLeft size={14} />
                        </button>
                    {/if}
                    {#if results.length === page_limit}
                        <button
                            type="button"
                            class="btn btn-sm preset-tonal-surface"
                            onclick={() => { page_offset += page_limit; do_search(false); }}>
                            <ChevronRight size={14} />
                        </button>
                    {/if}
                    <button
                        type="button"
                        onclick={() => do_search(false)}
                        class="btn btn-sm preset-tonal-surface"
                        title="Refresh">
                        <RefreshCw size={13} />
                    </button>
                </div>
            </div>

            <!-- Selection action bar — visible only when rows are checked -->
            {#if selected_ids.size > 0}
                <div class="border-surface-500/20 bg-error-500/10 flex flex-wrap items-center gap-3 border-b px-4 py-2">
                    <span class="text-error-700-300 text-xs font-bold">
                        {selected_ids.size} file{selected_ids.size !== 1 ? 's' : ''} selected
                    </span>
                    <button
                        type="button"
                        class="btn btn-sm preset-filled-error text-xs"
                        onclick={handle_bulk_delete}
                        disabled={bulk_deleting}
                        title="Delete all selected files (one confirmation for the whole batch)">
                        {#if bulk_deleting && bulk_delete_progress}
                            <LoaderCircle size={12} class="mr-1 animate-spin" />
                            Deleting {bulk_delete_progress.done}/{bulk_delete_progress.total}…
                        {:else}
                            <Trash2 size={12} class="mr-1" />
                            Delete {selected_ids.size} selected
                        {/if}
                    </button>
                    <button
                        type="button"
                        class="btn btn-sm preset-tonal-surface text-xs"
                        onclick={() => selected_ids.clear()}
                        disabled={bulk_deleting}>
                        <X size={12} class="mr-1" /> Clear selection
                    </button>
                </div>
            {/if}

            <div class="overflow-x-auto">
                <table class="w-full text-xs">
                    <thead>
                        <tr class="border-surface-500/20 border-b text-left text-[10px] font-bold uppercase tracking-wider opacity-50">
                            <th class="px-3 py-2 w-8">
                                <input
                                    type="checkbox"
                                    class="checkbox checkbox-sm"
                                    checked={all_visible_selected}
                                    onchange={toggle_select_all}
                                    title="Select / deselect all visible files" />
                            </th>
                            <th class="px-3 py-2">
                                <button type="button" class="flex items-center gap-1 hover:opacity-100" onclick={() => toggle_sort('filename')}>
                                    Filename
                                    {#if sort_col === 'filename'}
                                        {#if sort_dir === 'ASC'}<ChevronUp size={10} />{:else}<ChevronDown size={10} />{/if}
                                    {:else}
                                        <ArrowUpDown size={10} class="opacity-30" />
                                    {/if}
                                </button>
                            </th>
                            <th class="px-3 py-2">
                                <button type="button" class="flex items-center gap-1 hover:opacity-100" onclick={() => toggle_sort('size')}>
                                    Size
                                    {#if sort_col === 'size'}
                                        {#if sort_dir === 'ASC'}<ChevronUp size={10} />{:else}<ChevronDown size={10} />{/if}
                                    {:else}
                                        <ArrowUpDown size={10} class="opacity-30" />
                                    {/if}
                                </button>
                            </th>
                            <th class="hidden px-3 py-2 lg:table-cell">
                                <button type="button" class="flex items-center gap-1 hover:opacity-100" onclick={() => toggle_sort('created_on')}>
                                    Created
                                    {#if sort_col === 'created_on'}
                                        {#if sort_dir === 'ASC'}<ChevronUp size={10} />{:else}<ChevronDown size={10} />{/if}
                                    {:else}
                                        <ArrowUpDown size={10} class="opacity-30" />
                                    {/if}
                                </button>
                            </th>
                            <th class="hidden px-3 py-2 lg:table-cell">
                                <button type="button" class="flex items-center gap-1 hover:opacity-100" onclick={() => toggle_sort('updated_on')}>
                                    Updated
                                    {#if sort_col === 'updated_on'}
                                        {#if sort_dir === 'ASC'}<ChevronUp size={10} />{:else}<ChevronDown size={10} />{/if}
                                    {:else}
                                        <ArrowUpDown size={10} class="opacity-30" />
                                    {/if}
                                </button>
                            </th>
                            <th class="hidden px-3 py-2 lg:table-cell">SHA-256</th>
                            <th class="px-3 py-2"></th>
                        </tr>
                    </thead>
                    <tbody>
                        {#each displayed_results as file (file.hosted_file_id)}
                            <tr
                                class="border-surface-500/10 hover:bg-surface-500/5 border-b transition-colors duration-200"
                                class:opacity-50={!file.enable}
                                class:bg-error-100={selected_ids.has(file.hosted_file_id)}>
                                <td class="px-3 py-2">
                                    <input
                                        type="checkbox"
                                        class="checkbox checkbox-sm"
                                        checked={selected_ids.has(file.hosted_file_id)}
                                        onchange={() => toggle_select(file.hosted_file_id)} />
                                </td>
                                <td class="px-3 py-2">
                                    <div class="flex items-center gap-1.5">
                                        <File size={12} class="shrink-0 opacity-40" />
                                        <span class="font-mono">
                                            {(file as any).filename_w_ext ?? file.filename ?? '—'}
                                        </span>
                                        {#if !file.enable}
                                            <span class="badge preset-tonal-error ml-1 text-[9px]">off</span>
                                        {/if}
                                        {#if file.hide}
                                            <span class="badge preset-tonal-surface ml-1 text-[9px]">hidden</span>
                                        {/if}
                                        {#if orphan_ids.has(file.hosted_file_id)}
                                            <span class="badge preset-tonal-warning ml-1 text-[9px]" title="No links found — safe to delete">
                                                <Unlink size={9} class="mr-0.5" /> orphan
                                            </span>
                                        {/if}
                                    </div>
                                </td>
                                <td class="px-3 py-2 tabular-nums">{fmt_size(file.size)}</td>
                                <td class="hidden px-3 py-2 whitespace-nowrap opacity-50 lg:table-cell">
                                    {fmt_date(file.created_on)}
                                </td>
                                <td class="hidden px-3 py-2 whitespace-nowrap opacity-50 lg:table-cell">
                                    {fmt_date(file.updated_on)}
                                </td>
                                <!-- SHA-256: full hash on hover; copy button for server-side file tracing -->
                                <td class="hidden px-3 py-2 lg:table-cell">
                                    {#if file.hash_sha256}
                                        <div class="flex items-center gap-1">
                                            <span
                                                class="font-mono text-[10px] opacity-50"
                                                title={file.hash_sha256}>
                                                {file.hash_sha256.slice(0, 12)}…
                                            </span>
                                            <button
                                                type="button"
                                                class="btn btn-icon btn-sm preset-tonal-surface opacity-40 hover:opacity-100 transition-opacity"
                                                onclick={() => copy_hash(file.hash_sha256!)}
                                                title="Copy full SHA-256 to clipboard">
                                                <ClipboardCopy size={11} />
                                            </button>
                                        </div>
                                    {:else}
                                        <span class="opacity-30">—</span>
                                    {/if}
                                </td>
                                <td class="px-3 py-2">
                                    <div class="flex items-center gap-1">
                                        <button
                                            type="button"
                                            class="btn btn-sm text-xs transition-all
                                                {links_map.has(file.hosted_file_id)
                                                    ? 'preset-tonal-tertiary'
                                                    : 'preset-tonal-surface hover:preset-tonal-tertiary'}"
                                            onclick={() => toggle_links(file)}
                                            disabled={links_loading.get(file.hosted_file_id)}
                                            title="Show/hide file links">
                                            {#if links_loading.get(file.hosted_file_id)}
                                                <LoaderCircle size={12} class="animate-spin" />
                                            {:else}
                                                <Link size={12} />
                                            {/if}
                                        </button>
                                        <button
                                            type="button"
                                            class="btn btn-sm preset-tonal-surface hover:preset-tonal-primary text-xs transition-all"
                                            onclick={() => handle_download(file)}
                                            disabled={downloading_id === file.hosted_file_id}
                                            title="Download file">
                                            {#if downloading_id === file.hosted_file_id}
                                                <LoaderCircle size={12} class="animate-spin" />
                                            {:else}
                                                <Download size={12} />
                                            {/if}
                                        </button>
                                        <button
                                            type="button"
                                            class="btn btn-sm preset-tonal-error hover:preset-filled-error text-xs transition-all"
                                            onclick={() => handle_delete(file)}
                                            disabled={deleting_id === file.hosted_file_id}
                                            title="Delete file record and physical file (if orphaned)">
                                            {#if deleting_id === file.hosted_file_id}
                                                <LoaderCircle size={12} class="animate-spin" />
                                            {:else}
                                                <Trash2 size={12} />
                                            {/if}
                                        </button>
                                    </div>
                                </td>
                            </tr>
                            <!-- Links sub-row — shown when the link toggle is active -->
                            {#if links_map.has(file.hosted_file_id)}
                                {@const file_links = links_map.get(file.hosted_file_id) ?? []}
                                <tr class="border-surface-500/10 border-b bg-surface-50-950/50">
                                    <td colspan="7" class="px-4 py-2">
                                        {#if file_links.length === 0}
                                            <span class="text-[10px] italic opacity-40">No links found — file may be an orphan.</span>
                                        {:else}
                                            <div class="flex flex-wrap items-center gap-1.5">
                                                <span class="text-[10px] font-bold uppercase tracking-wider opacity-40">Linked to:</span>
                                                {#each file_links as lnk}
                                                    {@const link_key = `${lnk.link_to_type}:${lnk.link_to_id_random}`}
                                                    {@const link_url = link_url_map.get(link_key)}
                                                    {@const id_display = lnk.link_to_id_random ? lnk.link_to_id_random.slice(0, 8) + '…' : String(lnk.link_to_id)}
                                                    {@const full_id = lnk.link_to_id_random ?? String(lnk.link_to_id)}
                                                    {#if link_url}
                                                        <a
                                                            href={link_url}
                                                            target="_blank"
                                                            rel="noopener noreferrer"
                                                            class="badge preset-tonal-tertiary font-mono text-[10px] hover:preset-filled-tertiary transition-all"
                                                            title={`Open ${lnk.link_to_type} ${full_id}`}>
                                                            {lnk.link_to_type}
                                                            <span class="opacity-60 ml-0.5">/ {id_display}</span>
                                                            <span class="ml-0.5 opacity-40">↗</span>
                                                        </a>
                                                    {:else}
                                                        <span
                                                            class="badge preset-tonal-secondary font-mono text-[10px]"
                                                            title={full_id}>
                                                            {lnk.link_to_type}
                                                            <span class="opacity-60 ml-0.5">/ {id_display}</span>
                                                        </span>
                                                    {/if}
                                                {/each}
                                            </div>
                                        {/if}
                                    </td>
                                </tr>
                            {/if}
                        {/each}
                    </tbody>
                </table>
            </div>
        </div>

    {:else if searched && !loading}
        <div class="py-12 text-center opacity-50">
            <FileX size={48} class="mx-auto mb-4 opacity-30" />
            <p class="text-lg font-bold">No files found</p>
            <p class="text-sm">Try different filters.</p>
        </div>

    {:else if !searched && !loading}
        <div class="py-12 text-center opacity-30">
            <File size={48} class="mx-auto mb-4" />
            <p class="text-sm">Use the filters above to search for files.</p>
        </div>
    {/if}

</div>
