/**
 * HFTree — HuggingFace Repo Tree Visualizer
 * Core logic implementation as a TypeScript module.
 */

const HF_API = 'https://huggingface.co/api';

// Configuration
const FEATURED = [
    { repo: 'prithivMLmods/Qwen3-VL-4B-Instruct-Unredacted-MAX', type: 'models', label: 'Qwen3-VL-4B-Instruct-Unredacted-MAX' },
    { repo: 'prithivMLmods/Qwen3-VL-abliterated-MAX-Fast', type: 'spaces', label: 'Qwen3-VL Space' },
    { repo: 'prithivMLmods/LAP2-K-Think-v1.b', type: 'datasets', label: 'LAP2-K-Think' },
];

const POPULAR_MODELS = [
    { repo: 'meta-llama/Llama-3.1-8B-Instruct', type: 'models' },
    { repo: 'mistralai/Mistral-7B-Instruct-v0.3', type: 'models' },
    { repo: 'google/gemma-2-9b-it', type: 'models' },
    { repo: 'Qwen/Qwen2.5-7B-Instruct', type: 'models' },
    { repo: 'stabilityai/stable-diffusion-xl-base-1.0', type: 'models' },
    { repo: 'openai/whisper-large-v3', type: 'models' },
    { repo: 'microsoft/Phi-3-mini-4k-instruct', type: 'models' },
    { repo: 'black-forest-labs/FLUX.1-dev', type: 'models' },
];

const POPULAR_DATASETS = [
    { repo: 'tatsu-lab/alpaca', type: 'datasets' },
    { repo: 'databricks/databricks-dolly-15k', type: 'datasets' },
    { repo: 'Open-Orca/OpenOrca', type: 'datasets' },
    { repo: 'HuggingFaceFW/fineweb', type: 'datasets' },
    { repo: 'allenai/c4', type: 'datasets' },
];

let currentData: any[] = [];
let currentSort = 'folder-az';
let currentStyle = 'classic';
let currentType = 'models';
let currentRepo = '';
let currentBranch = 'main';
let filterText = '';

// DOM Cache
const els: any = {
    repo: document.getElementById('repoInput'),
    branch: document.getElementById('branchInput'),
    fetchBtn: document.getElementById('fetchBtn'),
    wrapper: document.getElementById('treeWrapper'),
    empty: document.getElementById('emptyState'),
    status: document.getElementById('statusMsg'),
    lineNums: document.getElementById('lineNumbers'),
    treeContent: document.getElementById('treeContent'),
    privateBtn: document.getElementById('privateRepoBtn'),
    tokenPanel: document.getElementById('tokenSection'),
    hfToken: document.getElementById('hfToken'),
    saveToken: document.getElementById('saveTokenBtn'),
    clearToken: document.getElementById('clearTokenBtn'),
    copyAll: document.getElementById('copyAllBtn'),
    shareBtn: document.getElementById('shareBtn'),
    overlay: document.getElementById('shareOverlay'),
    shareInput: document.getElementById('shareInput'),
    copyShareBtn: document.getElementById('copyShareBtn'),
    closeOverlay: document.getElementById('closeOverlay'),
    sortBtnLabel: document.getElementById('sortBtnLabel'),
    styleBtnLabel: document.getElementById('styleBtnLabel'),
    dropdowns: document.querySelectorAll('.dropdown'),
    urlPrefix: document.getElementById('urlPrefix'),
    typeModel: document.getElementById('typeModel'),
    typeDataset: document.getElementById('typeDataset'),
    typeSpace: document.getElementById('typeSpace'),
    repoInfoCard: document.getElementById('repoInfoCard'),
    repoTypeBadge: document.getElementById('repoTypeBadge'),
    repoInfoName: document.getElementById('repoInfoName'),
    repoInfoStats: document.getElementById('repoInfoStats'),
    treeFilter: document.getElementById('treeFilter'),
};

// --- Helpers ---

function getHeaders() {
    const t = localStorage.getItem('hft_token');
    const h: Record<string, string> = { 'Accept': 'application/json' };
    if (t && t.trim()) h['Authorization'] = `Bearer ${t.trim()}`;
    return h;
}

function getRepoPageUrl(type: string, repo: string) {
    if (type === 'datasets') return `https://huggingface.co/datasets/${repo}`;
    if (type === 'spaces') return `https://huggingface.co/spaces/${repo}`;
    return `https://huggingface.co/${repo}`;
}

function formatSize(bytes: number) {
    if (!bytes) return '0 B';
    const u = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(1024));
    return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${u[i]}`;
}

function escapeRegex(s: string) {
    return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function getFileIcon(name: string) {
    const ext = name.split('.').pop()?.toLowerCase() || '';
    const nl = name.toLowerCase();
    if (nl === 'dockerfile') return 'fa-brands fa-docker';
    if (nl === 'license' || nl === 'licence') return 'fa-solid fa-scale-balanced';
    if (nl === 'readme.md' || nl === 'readme') return 'fa-solid fa-book';
    if (nl === 'requirements.txt') return 'fa-solid fa-list-check';
    if (nl === '.gitignore' || nl === '.gitattributes') return 'fa-brands fa-git-alt';
    const map: Record<string, string> = {
        py: 'fa-brands fa-python', js: 'fa-brands fa-js', ts: 'fa-brands fa-js',
        jsx: 'fa-brands fa-react', tsx: 'fa-brands fa-react',
        html: 'fa-brands fa-html5', css: 'fa-brands fa-css3-alt',
        md: 'fa-solid fa-file-lines', json: 'fa-solid fa-file-code',
        yaml: 'fa-solid fa-file-code', yml: 'fa-solid fa-file-code', toml: 'fa-solid fa-file-code',
        txt: 'fa-solid fa-file-lines', csv: 'fa-solid fa-file-csv',
        png: 'fa-solid fa-file-image', jpg: 'fa-solid fa-file-image', jpeg: 'fa-solid fa-file-image',
        gif: 'fa-solid fa-file-image', svg: 'fa-solid fa-file-image', webp: 'fa-solid fa-file-image',
        mp4: 'fa-solid fa-file-video', mp3: 'fa-solid fa-file-audio', wav: 'fa-solid fa-file-audio',
        pdf: 'fa-solid fa-file-pdf', zip: 'fa-solid fa-file-zipper', tar: 'fa-solid fa-file-zipper', gz: 'fa-solid fa-file-zipper',
        safetensors: 'fa-solid fa-cube', bin: 'fa-solid fa-cube', gguf: 'fa-solid fa-cube',
        pt: 'fa-solid fa-cube', pth: 'fa-solid fa-cube', onnx: 'fa-solid fa-cube', h5: 'fa-solid fa-cube',
        parquet: 'fa-solid fa-database', arrow: 'fa-solid fa-database',
        rs: 'fa-brands fa-rust', go: 'fa-brands fa-golang', java: 'fa-brands fa-java',
        sh: 'fa-solid fa-terminal', bash: 'fa-solid fa-terminal',
    };
    return map[ext] || 'fa-regular fa-file';
}

// --- API Methods ---

async function fetchTree(type: string, repo: string, branch: string, path: string = '') {
    const url = path
        ? `${HF_API}/${type}/${repo}/tree/${branch}/${path}`
        : `${HF_API}/${type}/${repo}/tree/${branch}`;
    const resp = await fetch(url, { headers: getHeaders() });
    if (!resp.ok) {
        if (resp.status === 401) throw new Error('401: Unauthorized. Check your token.');
        if (resp.status === 403) throw new Error('403: Forbidden / Rate Limited.');
        if (resp.status === 404) {
            if (branch === 'main' && !path) {
                const r2 = await fetch(`${HF_API}/${type}/${repo}/tree/master`, { headers: getHeaders() });
                if (r2.ok) return { items: await r2.json(), switchedBranch: 'master' };
            }
            throw new Error('404: Repository not found (or is private).');
        }
        throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
    }
    return { items: await resp.json(), switchedBranch: null };
}

async function fetchFullTree(type: string, repo: string, branch: string) {
    let all: any[] = [], switched: string | null = null;
    async function recurse(path: string) {
        const r = await fetchTree(type, repo, switched || branch, path);
        if (r.switchedBranch && !switched) switched = r.switchedBranch;
        for (const item of r.items) {
            all.push({
                path: item.path,
                type: item.type === 'directory' ? 'tree' : 'blob',
                size: item.size || 0,
                lfs: item.lfs || null,
            });
            if (item.type === 'directory') {
                try { await recurse(item.path); }
                catch (e: any) { console.warn(`Skip ${item.path}: ${e.message}`); }
            }
        }
    }
    await recurse('');
    return { tree: all, branch: switched || branch };
}

// --- UI Methods ---

function buildVisualHierarchy(sortedList: any[]) {
    const root: any = { children: [] }, map: any = { '': root };
    sortedList.forEach(item => { map[item.path] = { ...item, name: item.path.split('/').pop(), children: [] }; });
    sortedList.forEach(item => {
        const pts = item.path.split('/'); pts.pop();
        (map[pts.join('/')] || root).children.push(map[item.path]);
    });
    const result: any[] = [];
    function traverse(node: any, prefix: string, isLast: boolean) {
        const conn = isLast ? "└── " : "├── ";
        result.push({
            name: node.name, path: node.path, type: node.type,
            size: node.size, lfs: node.lfs,
            prefix: prefix + conn,
            indent: prefix.replace(/│   /g, '    ').replace(/├── /g, '    ').replace(/└── /g, '    ') + '    '
        });
        if (node.children && node.children.length) {
            const cp = prefix + (isLast ? "    " : "│   ");
            node.children.forEach((c: any, i: number) => traverse(c, cp, i === node.children.length - 1));
        }
    }
    root.children.forEach((c: any, i: number) => traverse(c, "", i === root.children.length - 1));
    return result;
}

function sortTree(data: any[], mode: string) {
    const s = [...data];
    s.sort((a, b) => {
        const ad = a.type === 'tree' ? 1 : 0, bd = b.type === 'tree' ? 1 : 0;
        const an = a.path.toLowerCase(), bn = b.path.toLowerCase();
        switch (mode) {
            case 'folder-az': if (ad !== bd) return bd - ad; return an.localeCompare(bn);
            case 'folder-za': if (ad !== bd) return bd - ad; return bn.localeCompare(an);
            case 'alpha-az': return an.localeCompare(bn);
            case 'alpha-za': return bn.localeCompare(an);
            case 'size-desc': return (b.size || 0) - (a.size || 0);
            case 'size-asc': return (a.size || 0) - (b.size || 0);
            default: return 0;
        }
    });
    return s;
}

function render() {
    els.lineNums.innerHTML = '';
    els.treeContent.innerHTML = '';
    let data = currentData;
    if (filterText) {
        const mp = new Set();
        currentData.forEach(item => {
            if (item.path.toLowerCase().includes(filterText)) {
                mp.add(item.path);
                const pts = item.path.split('/');
                for (let i = 1; i < pts.length; i++) mp.add(pts.slice(0, i).join('/'));
            }
        });
        data = currentData.filter(i => mp.has(i.path));
    }
    const sorted = sortTree(data, currentSort);
    const hier = buildVisualHierarchy(sorted);
    const fn = document.createDocumentFragment(), fl = document.createDocumentFragment();
    hier.forEach((item, idx) => {
        const n = document.createElement('div');
        n.textContent = (idx + 1).toString();
        fn.appendChild(n);
        const row = document.createElement('div');
        row.className = 'tree-line';
        row.style.animation = `fadeIn 0.1s forwards ${Math.min(idx * 2, 500)}ms`;
        row.style.opacity = '0';
        const ic = item.type === 'tree' ? 'fa-solid fa-folder' : getFileIcon(item.name);
        const tc = item.type === 'tree' ? 'folder' : 'file';
        let pre = item.prefix, nm = item.name;
        if (currentStyle === 'minimal') pre = item.indent;
        if (currentStyle === 'plus') pre = pre.replace(/└──/g, '+--').replace(/├──/g, '+--').replace(/│/g, '|');
        if (currentStyle === 'slashed') {
            if (item.type === 'tree') nm = `/${item.name}`;
            pre = item.prefix.replace(/│/g, ' ').replace(/├──/g, ' ').replace(/└──/g, ' ');
        }
        const sz = item.type === 'blob' && item.size ? `<span class="t-size">${formatSize(item.size)}</span>` : '';
        const lf = item.lfs ? `<span class="lfs-badge">LFS</span>` : '';
        let nh = nm;
        if (filterText && nm.toLowerCase().includes(filterText)) {
            const rx = new RegExp(`(${escapeRegex(filterText)})`, 'gi');
            nh = nm.replace(rx, '<mark style="background:var(--accent-light);color:var(--accent);padding:0 2px;border-radius:2px;">$1</mark>');
        }
        row.innerHTML = `<span class="t-prefix">${pre}</span><span class="t-icon"><i class="${ic}"></i></span><span class="t-name ${tc}">${nh}</span>${sz}${lf}<button class="sub-copy" data-path="${item.path}" title="Copy Path"><i class="far fa-copy"></i></button>`;
        fl.appendChild(row);
    });
    els.lineNums.appendChild(fn);
    els.treeContent.appendChild(fl);
}

function showMsg(text: string, type: string) {
    els.status.style.display = text ? 'block' : 'none';
    els.status.innerHTML = text;
    els.status.className = type;
}

function showRepoInfo(repo: string, branch: string) {
    const tc = currentType === 'models' ? 'model' : currentType === 'datasets' ? 'dataset' : 'space';
    const tl = currentType === 'models' ? 'MODEL' : currentType === 'datasets' ? 'DATASET' : 'SPACE';
    els.repoTypeBadge.className = `repo-type-badge ${tc}`;
    els.repoTypeBadge.textContent = tl;
    els.repoInfoName.innerHTML = `<a href="${getRepoPageUrl(currentType, repo)}" target="_blank" rel="noopener">${repo}</a>`;
    const fc = currentData.filter(i => i.type === 'blob').length;
    const dc = currentData.filter(i => i.type === 'tree').length;
    const ts = currentData.reduce((a, i) => a + (i.size || 0), 0);
    els.repoInfoStats.innerHTML = `
        <span><i class="far fa-file"></i> ${fc} files</span>
        <span><i class="far fa-folder"></i> ${dc} folders</span>
        <span><i class="fas fa-weight-hanging"></i> ${formatSize(ts)}</span>
        <span><i class="fas fa-code-branch"></i> ${branch}</span>`;
    els.repoInfoCard.classList.add('visible');
}

async function loadTree() {
    const repo = els.repo.value.trim();
    const branch = els.branch.value.trim() || 'main';
    if (!repo.includes('/')) return showMsg("Invalid format. Use 'username/repo-name'", "error");
    currentRepo = repo; currentBranch = branch;
    filterText = ''; els.treeFilter.value = '';
    if (els.empty) els.empty.style.display = 'none';
    els.wrapper.style.display = 'none';
    els.repoInfoCard.classList.remove('visible');
    showMsg(`<span class="spinner"></span> Fetching ${currentType}/${repo} (${branch})…`, "loading");
    els.fetchBtn.disabled = true;
    try {
        const ck = `hft_${currentType}_${repo}_${branch}`;
        const ta = !!localStorage.getItem('hft_token');
        const cached = !ta ? sessionStorage.getItem(ck) : null;
        if (cached) {
            const p = JSON.parse(cached);
            currentData = p.tree; currentBranch = p.branch;
            showMsg("", "");
        } else {
            const r = await fetchFullTree(currentType, repo, branch);
            currentData = r.tree; currentBranch = r.branch;
            if (r.branch !== branch) {
                showMsg(`Branch '${branch}' not found → '${r.branch}'.`, "loading");
                els.branch.value = r.branch;
            } else showMsg("", "");
            if (!ta) try { sessionStorage.setItem(ck, JSON.stringify({ tree: currentData, branch: currentBranch })); } catch (e) { }
        }
        window.location.hash = `${currentType}/${repo}/${currentBranch}`;
        document.title = `${repo} — HFTree`;
        showRepoInfo(repo, currentBranch);
        render();
        els.wrapper.style.display = 'flex';
    } catch (err: any) {
        console.error(err);
        const m = err.message;
        if (m.includes('401')) {
            showMsg(`
                <i class="fas fa-lock"></i> <b>Unauthorized (401)</b><br>
                Check your HuggingFace token or Provide one.
            `, "error");
        } else if (m.includes('403')) {
            showMsg("Rate limited or forbidden. Try adding a token.", "error");
        } else if (m.includes('404')) {
            showMsg("Repository not found. Check name and type.", "error");
        } else {
            showMsg(m, "error");
        }
        els.empty.style.display = 'block';
    } finally { els.fetchBtn.disabled = false; }
}

function handleCopy(btn: any, text: string, isSub: boolean) {
    navigator.clipboard.writeText(text).then(() => {
        if (!isSub) {
            const oh = btn.innerHTML;
            btn.innerHTML = `<i class="fas fa-check"></i> Copied`;
            setTimeout(() => { btn.innerHTML = oh; }, 1500);
        } else {
            const ic = btn.querySelector('i'), oc = ic.className;
            ic.className = "fas fa-check";
            setTimeout(() => { ic.className = oc; }, 1500);
        }
    }).catch(e => console.error("Copy failed", e));
}

function generateAsciiTree(data: any[]) {
    const sorted = sortTree(data, currentSort);
    const hier = buildVisualHierarchy(sorted);
    const lines = [`${currentRepo} (${currentBranch})`];
    hier.forEach(item => {
        let pre = item.prefix, nm = item.name;
        if (currentStyle === 'minimal') pre = item.indent;
        if (currentStyle === 'plus') pre = pre.replace(/└──/g, '+--').replace(/├──/g, '+--').replace(/│/g, '|');
        if (currentStyle === 'slashed') {
            if (item.type === 'tree') nm = `/${nm}`;
            pre = item.prefix.replace(/│/g, ' ').replace(/├──/g, ' ').replace(/└──/g, ' ');
        }
        const sz = item.type === 'blob' && item.size ? ` (${formatSize(item.size)})` : '';
        lines.push(`${pre}${nm}${sz}`);
    });
    return lines.join('\n');
}

function setType(type: string) {
    currentType = type;
    [els.typeModel, els.typeDataset, els.typeSpace].forEach(b => b.classList.remove('active-model', 'active-dataset', 'active-space'));
    if (type === 'models') { els.typeModel.classList.add('active-model'); els.urlPrefix.textContent = 'hf.co/'; }
    if (type === 'datasets') { els.typeDataset.classList.add('active-dataset'); els.urlPrefix.textContent = 'hf.co/datasets/'; }
    if (type === 'spaces') { els.typeSpace.classList.add('active-space'); els.urlPrefix.textContent = 'hf.co/spaces/'; }
}

function checkSavedToken() {
    const t = localStorage.getItem('hft_token');
    if (t) {
        els.hfToken.value = t;
        els.privateBtn.innerHTML = `<i class="fas fa-lock-open"></i> Active`;
        els.privateBtn.style.color = 'var(--accent)';
        els.saveToken.style.display = 'none';
        els.clearToken.style.display = 'inline-block';
    } else {
        els.privateBtn.innerHTML = `<i class="fas fa-lock"></i> Token`;
        els.privateBtn.style.color = '';
        els.saveToken.style.display = 'inline-block';
        els.clearToken.style.display = 'none';
    }
}

// --- Init ---

function init() {
    // Theme
    const savedTheme = localStorage.getItem('hft_theme') || 'light';
    document.documentElement.setAttribute('data-theme', savedTheme);
    document.getElementById('themeToggle')?.addEventListener('click', () => {
        const n = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
        document.documentElement.setAttribute('data-theme', n);
        localStorage.setItem('hft_theme', n);
    });

    checkSavedToken();

    // Dropdowns
    [els.sortBtnLabel, els.styleBtnLabel].forEach(btn => {
        btn.addEventListener('click', (e: any) => {
            e.stopPropagation();
            const p = btn.parentElement;
            els.dropdowns.forEach((d: any) => { if (d !== p) d.classList.remove('active'); });
            p.classList.toggle('active');
        });
    });
    document.querySelectorAll('[data-sort]').forEach(el => {
        el.addEventListener('click', (e: any) => {
            e.preventDefault();
            currentSort = e.target.dataset.sort;
            els.sortBtnLabel.innerHTML = `<i class="fas fa-sort-amount-down"></i> ${e.target.innerText}`;
            els.dropdowns.forEach((d: any) => d.classList.remove('active'));
            render();
        });
    });
    document.querySelectorAll('[data-style]').forEach(el => {
        el.addEventListener('click', (e: any) => {
            e.preventDefault();
            currentStyle = e.target.dataset.style;
            els.styleBtnLabel.innerHTML = `<i class="fas fa-code-branch"></i> ${e.target.innerText}`;
            els.dropdowns.forEach((d: any) => d.classList.remove('active'));
            render();
        });
    });
    document.addEventListener('click', () => els.dropdowns.forEach((d: any) => d.classList.remove('active')));

    // Event Listeners
    els.fetchBtn.addEventListener('click', loadTree);
    els.copyAll.addEventListener('click', () => handleCopy(els.copyAll, generateAsciiTree(currentData), false));
    els.privateBtn.addEventListener('click', () => {
        els.tokenPanel.style.display = els.tokenPanel.style.display === 'none' ? 'block' : 'none';
    });
    els.saveToken.addEventListener('click', () => {
        const t = els.hfToken.value.trim();
        if (t) localStorage.setItem('hft_token', t);
        checkSavedToken();
        showMsg("Token saved.", "loading");
        setTimeout(() => showMsg("", ""), 1500);
    });
    els.clearToken.addEventListener('click', () => {
        localStorage.removeItem('hft_token');
        els.hfToken.value = '';
        checkSavedToken();
        showMsg("Token cleared.", "loading");
        setTimeout(() => showMsg("", ""), 1500);
    });
    [els.typeModel, els.typeDataset, els.typeSpace].forEach(b =>
        b.addEventListener('click', () => setType(b.dataset.type))
    );
    els.treeFilter.addEventListener('input', (e: any) => { filterText = e.target.value.trim().toLowerCase(); render(); });
    els.treeContent.onclick = (e: any) => {
        const b = e.target.closest('.sub-copy');
        if (b) handleCopy(b, b.dataset.path, true);
    };

    // Share Overlay
    els.shareBtn.addEventListener('click', (e: any) => {
        e.stopPropagation();
        els.shareInput.value = window.location.href;
        els.overlay.classList.add('visible');
    });
    els.closeOverlay.addEventListener('click', () => els.overlay.classList.remove('visible'));
    els.overlay.addEventListener('click', (e: any) => { if (e.target === els.overlay) els.overlay.classList.remove('visible'); });
    els.shareInput.addEventListener('click', () => els.shareInput.select());
    els.copyShareBtn.addEventListener('click', () => handleCopy(els.copyShareBtn, els.shareInput.value, true));

    // Featured Tags
    const createRepoTag = (label: string, type: string, repo: string) => {
        const btn = document.createElement('button');
        btn.className = 'repo-tag';
        const tc = type === 'models' ? 'model' : type === 'datasets' ? 'dataset' : 'space';
        const tl = type === 'models' ? 'MODEL' : type === 'datasets' ? 'DATA' : 'SPACE';
        btn.innerHTML = `<span class="tag-type ${tc}">${tl}</span> ${label}`;
        btn.addEventListener('click', () => {
            setType(type); els.repo.value = repo; els.branch.value = 'main'; loadTree();
        });
        return btn;
    };
    FEATURED.forEach(i => document.getElementById('featuredCloud')?.appendChild(createRepoTag(i.label || i.repo, i.type, i.repo)));
    POPULAR_MODELS.forEach(i => document.getElementById('modelCloud')?.appendChild(createRepoTag(i.repo, i.type, i.repo)));
    POPULAR_DATASETS.forEach(i => document.getElementById('datasetCloud')?.appendChild(createRepoTag(i.repo, i.type, i.repo)));

    // Hash check
    if (window.location.hash && window.location.hash.startsWith('#')) {
        const p = window.location.hash.substring(1).split('/');
        if (p.length >= 3 && ['models', 'datasets', 'spaces'].includes(p[0])) {
            setType(p[0]);
            els.repo.value = p[1] + '/' + p[2];
            els.branch.value = p[3] || 'main';
            loadTree();
        }
    }
}

init();