/**
 * knowledge.js — Knowledge view: RAG collections list + topology panel.
 */

import { S }          from '../state.js';
import { api, post, bustCache } from '../api.js';
import { openDrawer } from '../widgets/drawer.js';

const $ = id => document.getElementById(id);
const esc = s => s==null?'':String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');

function fmtNum(n) {
    if (n==null||isNaN(n)) return '—';
    const v=Number(n);
    if (v>=1e6) return (v/1e6).toFixed(1)+'M';
    if (v>=1000) return (v/1000).toFixed(1)+'K';
    return String(Math.round(v));
}

function collectionIdOf(collection) {
    return collection?.collectionId || collection?.id || '';
}

function collectionNameOf(collection) {
    return collection?.name || collectionIdOf(collection) || 'collection';
}

function collectionCounts(collection) {
    const sources = collection?.sourceCount ?? collection?.documentCount ?? collection?.count ?? 0;
    const chunks = collection?.chunkCount ?? 0;
    return `${fmtNum(sources)} sources · ${fmtNum(chunks)} chunks`;
}

/* ── Data loader ── */
export async function load() {
    const [rags, topo] = await Promise.all([
        api('/api/rag/collections', 15000),
        api('/api/knowledge-topology', 30000),
    ]);
    S.ragCollections = Array.isArray(rags) ? rags : (rags?.collections||[]);
    S.topology       = topo&&!topo.error ? topo : null;
    render();
}

export function render() {
    renderRagCollections();
    renderTopologyStats();
}

/* ── RAG Collections ── */
function renderRagCollections() {
    const el=$('rag-collections'); if (!el) return;
    const cols=S.ragCollections;
    if (!cols.length) {
        el.innerHTML=`<div class="empty">
            <div class="empty-title">No RAG collections</div>
            <div class="empty-sub">Ingest documents to build a knowledge base for this repo.</div>
            <button class="btn btn-sm empty-cta" id="rag-create-btn">Create collection</button>
        </div>`;
        $('rag-create-btn')?.addEventListener('click', _showCreateSheet);
        return;
    }
    el.innerHTML=`<div style="margin-bottom:8px;display:flex;align-items:center;gap:8px;justify-content:space-between;flex-wrap:wrap">
        <span style="font-size:var(--text-sm);color:var(--text-muted)">Attach collections to feed the orchestrator and dashboard chat.</span>
        <button class="btn btn-sm" id="rag-create-btn">New collection</button>
    </div>
    <div>${cols.map(c=> {
        const id = collectionIdOf(c);
        const name = collectionNameOf(c);
        const attached = (c.attachedRuntimeIds || []).length || (c.attachedSessionIds || []).length;
        return `<div class="mission-row">
        <div class="mission-body">
            <div class="mission-title">${esc(name)}</div>
            <div class="mission-meta" style="font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-dim)">
                ${collectionCounts(c)} · ${attached ? 'attached' : 'detached'}
            </div>
        </div>
        <button class="btn btn-sm" data-ingest="${esc(id)}" data-ingestname="${esc(name)}">Add source</button>
        <button class="btn btn-sm" data-ingest-repo="${esc(id)}" data-ingestname="${esc(name)}">Ingest repo</button>
        <button class="btn btn-sm" data-attach="${esc(id)}" data-attached="${attached ? '1' : '0'}">${attached ? 'Detach' : 'Attach'}</button>
        <button class="btn btn-sm" data-drop-target="${esc(id)}" data-drop-name="${esc(name)}" title="Drop files to upload">Upload</button>
    </div>`;
    }).join('')}</div>
    ${_renderDropZone(cols)}`;

    $('rag-create-btn')?.addEventListener('click', _showCreateSheet);
    el.querySelectorAll('[data-ingest]').forEach(btn => {
        btn.addEventListener('click', () => _showIngestSheet(btn.dataset.ingest, btn.dataset.ingestname));
    });
    el.querySelectorAll('[data-ingest-repo]').forEach(btn => {
        btn.addEventListener('click', () => _ingestRepoCollection(btn.dataset.ingestRepo, btn.dataset.ingestname, btn));
    });
    el.querySelectorAll('[data-attach]').forEach(btn => {
        btn.addEventListener('click', () => _toggleCollectionAttach(btn.dataset.attach, btn.dataset.attached === '1', btn));
    });
    el.querySelectorAll('[data-drop-target]').forEach(btn => {
        btn.addEventListener('click', () => _focusDropZone(btn.dataset.dropTarget, btn.dataset.dropName));
    });
    _attachDropZone(cols);
}

/** Render the drag-and-drop upload zone below the collection list. */
function _renderDropZone(cols) {
    const opts = cols.map(c=>`<option value="${esc(collectionIdOf(c))}">${esc(collectionNameOf(c))}</option>`).join('');
    return `
    <div id="rag-drop-zone" class="rag-drop-zone" aria-label="Drop files to ingest" role="region">
      <div class="rag-drop-inner" id="rag-drop-inner">
        <span class="rag-drop-icon">📄</span>
        <span class="rag-drop-label">Drop files here to ingest</span>
        <span class="rag-drop-hint">or <label for="rag-file-input" class="rag-file-link">browse</label></span>
        <input id="rag-file-input" type="file" multiple style="display:none" accept=".md,.txt,.json,.csv,.html,.ts,.js,.py">
      </div>
      <div style="margin-top:10px;display:flex;align-items:center;gap:8px;flex-wrap:wrap">
        <label style="font-size:var(--text-sm);color:var(--text-muted)">Target collection:</label>
        <select id="rag-drop-collection" class="form-input" style="min-width:160px;font-size:var(--text-sm)">${opts}</select>
      </div>
      <div id="rag-drop-progress" style="margin-top:8px;font-size:var(--text-sm);color:var(--text-muted)"></div>
    </div>`;
}

function _focusDropZone(collectionId, collectionName) {
    const sel = $('rag-drop-collection');
    if (sel) sel.value = collectionId;
    const zone = $('rag-drop-zone');
    if (zone) zone.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    _toast(`Drop files to ingest into "${collectionName}"`, 'info');
}

function _attachDropZone(cols) {
    const zone = $('rag-drop-zone');
    const fileInput = $('rag-file-input');
    if (!zone) return;

    ['dragenter','dragover'].forEach(evt => {
        zone.addEventListener(evt, e => { e.preventDefault(); zone.classList.add('rag-drop-over'); });
    });
    ['dragleave','dragend'].forEach(evt => {
        zone.addEventListener(evt, () => zone.classList.remove('rag-drop-over'));
    });
    zone.addEventListener('drop', e => {
        e.preventDefault();
        zone.classList.remove('rag-drop-over');
        const files = Array.from(e.dataTransfer?.files ?? []);
        if (files.length) _uploadFiles(files);
    });
    if (fileInput) {
        fileInput.addEventListener('change', e => {
            const files = Array.from(e.target.files ?? []);
            if (files.length) _uploadFiles(files);
            fileInput.value = '';
        });
    }
}

async function _uploadFiles(files) {
    const collectionId = $('rag-drop-collection')?.value;
    if (!collectionId) { _toast('Select a target collection first', 'bad'); return; }

    const progress = $('rag-drop-progress');
    if (progress) progress.textContent = `Reading ${files.length} file(s)…`;

    /** Read a File object as text using FileReader. */
    function readFileText(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload  = e => resolve(e.target.result);
            reader.onerror = () => reject(new Error(`Cannot read ${file.name}`));
            reader.readAsText(file);
        });
    }

    const inputs = [];
    for (const file of files) {
        try {
            const text = await readFileText(file);
            inputs.push({ text, label: file.name });
        } catch (err) {
            _toast(`Skipped ${file.name}: ${err.message}`, 'warn');
        }
    }

    if (!inputs.length) { if (progress) progress.textContent = ''; return; }
    if (progress) progress.textContent = `Ingesting ${inputs.length} file(s)…`;

    const r = await post(`/api/rag/collections/${encodeURIComponent(collectionId)}/ingest`, { inputs }).catch(() => null);

    if (progress) progress.textContent = '';
    if (r?.ok) {
        _toast(`Ingested ${inputs.length} file(s) into collection.`, 'good');
        bustCache('/api/rag/collections');
        setTimeout(load, 1200);
    } else {
        _toast(`Ingest failed: ${r?.error ?? 'unknown error'}`, 'bad');
    }
}

async function _ingestRepoCollection(id, name, btn) {
    btn.disabled=true; btn.textContent='Ingesting…';
    const r=await post('/api/rag/ingest', { collectionId: id });
    btn.disabled=false; btn.textContent='Ingest repo';
    if (r.ok) {
        const added = r.data?.sourcesAdded ?? r.data?.chunksAdded ?? 0;
        _toast(`Collection "${name}" ingested ${added ? fmtNum(added) : 'repo'} source(s).`, 'good');
        bustCache('/api/rag/collections');
        setTimeout(load, 1500);
    } else {
        _toast(`Ingest failed: ${r.error}`, 'bad');
    }
}

async function _toggleCollectionAttach(id, attached, btn) {
    btn.disabled = true;
    btn.textContent = attached ? 'Detaching…' : 'Attaching…';
    const r = await post(`/api/rag/collections/${encodeURIComponent(id)}/${attached ? 'detach' : 'attach'}`, {});
    if (r.ok) {
        _toast(attached ? 'Collection detached.' : 'Collection attached.', 'good');
        bustCache('/api/rag/collections');
        setTimeout(load, 700);
    } else {
        btn.disabled = false;
        btn.textContent = attached ? 'Detach' : 'Attach';
        _toast(`Collection update failed: ${r.error}`, 'bad');
    }
}

function _showIngestSheet(collectionId, collectionName) {
    openDrawer({
        title: `Add source to ${collectionName}`,
        body: `<div class="dsec">
            <div class="dsec-title">Ingest source</div>
            <label class="form-label" for="rag-ingest-url">URL</label>
            <input id="rag-ingest-url" class="form-input" type="url" placeholder="https://docs.example.com/page">
            <label class="form-label" for="rag-ingest-file" style="margin-top:10px">File or folder path</label>
            <input id="rag-ingest-file" class="form-input" type="text" placeholder="${esc(S.workspace?.repoRoot || '/path/to/repo')}">
            <label class="form-label" for="rag-ingest-text" style="margin-top:10px">Text note</label>
            <textarea id="rag-ingest-text" class="form-input" rows="5" placeholder="Paste notes, API docs, or decision context"></textarea>
            <label class="form-label" for="rag-ingest-tags" style="margin-top:10px">Tags</label>
            <input id="rag-ingest-tags" class="form-input" type="text" placeholder="repo,dashboard">
            <button class="btn btn-primary" id="rag-ingest-confirm" style="margin-top:var(--space-4)">Ingest source</button>
            <div id="rag-ingest-status" style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--text-muted)"></div>
        </div>`,
    });
    $('rag-ingest-confirm')?.addEventListener('click', () => _submitIngest(collectionId));
}

async function _submitIngest(collectionId) {
    const status = $('rag-ingest-status');
    const url = ($('rag-ingest-url')?.value || '').trim();
    const fileOrFolder = ($('rag-ingest-file')?.value || '').trim();
    const text = ($('rag-ingest-text')?.value || '').trim();
    const tags = ($('rag-ingest-tags')?.value || '').split(',').map(s => s.trim()).filter(Boolean);
    const inputs = [];
    if (url) inputs.push({ url, tags });
    if (fileOrFolder) inputs.push({ folderPath: fileOrFolder, filePath: fileOrFolder, tags });
    if (text) inputs.push({ text, label: 'dashboard-note', tags });
    if (!inputs.length) {
        if (status) { status.textContent = 'Add a URL, file/folder path, or text note.'; status.style.color = 'var(--bad)'; }
        return;
    }
    if (status) { status.textContent = 'Ingesting…'; status.style.color = 'var(--text-muted)'; }
    const r = await post(`/api/rag/collections/${encodeURIComponent(collectionId)}/ingest`, { inputs }).catch(() => null);
    if (r?.ok) {
        if (status) { status.textContent = `Added ${r.data?.sourcesAdded ?? inputs.length} source(s).`; status.style.color = 'var(--ok)'; }
        _toast('RAG source ingested.', 'good');
        bustCache('/api/rag/collections');
        setTimeout(load, 900);
    } else if (status) {
        status.textContent = `Ingest failed: ${r?.error ?? 'unknown error'}`;
        status.style.color = 'var(--bad)';
    }
}

function _showCreateSheet() {
    openDrawer({
        title: 'Create collection',
        body: `<div class="dsec">
            <div class="dsec-title">New RAG collection</div>
            <label class="form-label" for="rag-new-name">Collection name</label>
            <input id="rag-new-name" class="form-input" type="text" placeholder="${esc(S.workspace?.repoName || 'repo-knowledge')}" autocomplete="off">
            <button class="btn btn-primary" id="rag-create-confirm" style="margin-top:var(--space-4)">Create collection</button>
            <div id="rag-create-status" style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--text-muted)"></div>
        </div>`,
    });
    const submit = async () => {
        const nameInput = $('rag-new-name');
        const status = $('rag-create-status');
        const name = (nameInput?.value || '').trim();
        if (!name) {
            if (status) { status.textContent = 'Collection name is required.'; status.style.color = 'var(--bad)'; }
            return;
        }
        if (status) { status.textContent = 'Creating collection…'; status.style.color = 'var(--text-muted)'; }
        const r = await post('/api/rag/collections', { name }).catch(() => null);
        if (r?.ok) {
            if (status) { status.textContent = `Collection "${name}" created.`; status.style.color = 'var(--ok)'; }
            _toast(`Collection "${name}" created.`, 'good');
            bustCache('/api/rag/collections');
            setTimeout(load, 800);
        } else if (status) {
            status.textContent = `Create failed: ${r?.error ?? 'unknown error'}`;
            status.style.color = 'var(--bad)';
        }
    };
    $('rag-create-confirm')?.addEventListener('click', submit);
    $('rag-new-name')?.addEventListener('keydown', e => {
        if (e.key === 'Enter') submit();
    });
}

/* ── Topology stats ── */
function renderTopologyStats() {
    const el=$('topo-stats'); if (!el) return;
    const t=S.topology;
    if (!t) {
        el.innerHTML=`<div class="empty">
            <div class="empty-title">Knowledge graph not built yet.</div>
            <button class="btn btn-sm empty-cta" id="topo-build-btn">Build now</button>
        </div>`;
        $('topo-build-btn')?.addEventListener('click', _buildTopology);
        return;
    }
    const m = t.meta || {};
    const rows=[
        ['Nodes',    fmtNum(t.nodes?.length)],
        ['Edges',    fmtNum(t.edges?.length)],
        ['Files',    fmtNum(m.fileCount   ?? t.nodes?.filter(n=>n.type==='file').length)],
        ['Modules',  fmtNum(m.moduleCount ?? t.nodes?.filter(n=>n.type==='module').length)],
        ['Memories', fmtNum(m.memoryCount ?? t.nodes?.filter(n=>n.type==='memory').length)],
        ['Concepts', fmtNum(m.conceptCount ?? t.nodes?.filter(n=>n.type==='concept').length)],
        ['Source',   t.provenance?.graphSource || m.graphSource || '—'],
    ];
    el.innerHTML=rows.map(([k,v])=>`<div class="row"><span class="row-k">${esc(k)}</span><span class="row-v">${esc(v)}</span></div>`).join('');
}

async function _buildTopology() {
    const btn=$('topo-build-btn'); if (btn) { btn.disabled=true; btn.textContent='Building…'; }
    await post('/api/knowledge-topology/build', {});
    bustCache('/api/knowledge-topology');
    setTimeout(load, 2000);
}

function _toast(msg, kind='info') {
    const stack=document.getElementById('toast-stack'); if (!stack) return;
    const t=document.createElement('div');
    t.className='toast toast-'+kind; t.textContent=msg;
    stack.appendChild(t);
    setTimeout(()=>{ t.style.transition='opacity 0.2s'; t.style.opacity='0'; setTimeout(()=>t.remove(),220); },4000);
}
