#!/usr/bin/env python3
"""
Maritime QA Comparison Demo
RAG vs Wiki side-by-side comparison
With server-side conversation history persistence
"""
import json
import os
import sys
import time
import threading
from datetime import datetime
from flask import Flask, request, jsonify, render_template_string

import yaml

# Load paths from agent config
_agent_cfg_path = os.environ.get('AGENT_CONFIG', '/app/agentexample/qaagent67/agent-config.yml')
_wiki_cfg_path = os.environ.get('WIKI_CONFIG', '/app/agentexample/qaagent67wiki/agent-config.yml')
_cfg = {}
_wiki_cfg = {}
if os.path.exists(_agent_cfg_path):
    with open(_agent_cfg_path) as f:
        _cfg = yaml.safe_load(f)
if os.path.exists(_wiki_cfg_path):
    with open(_wiki_cfg_path) as f:
        _wiki_cfg = yaml.safe_load(f)

_knowledge = _cfg.get('knowledge', {})
KNOWLEDGE_BASE = _knowledge.get('baseDir', os.environ.get('KNOWLEDGE_DIR', '/data/knowledge/maritime'))

sys.path.insert(0, KNOWLEDGE_BASE)

try:
    from search_unified import search
except ImportError:
    from search_engine import search
from lambdagent.providers import create_provider

app = Flask(__name__)

HISTORY_FILE = os.environ.get('HISTORY_FILE', '/app/qademo/conversation_history.json')
WIKI_DIR = _wiki_cfg.get('wiki', {}).get('dir', '/app/agentexample/qaagent67wiki/wiki')

# LLM provider (lazy init)
_provider = None
def get_provider():
    global _provider
    if _provider is None:
        _provider = create_provider('ollama', model='qwen2.5-32b', base_url='http://127.0.0.1:8000/v1', timeout=120)
    return _provider

# ── Conversation history persistence ──
_history_lock = threading.Lock()

def load_history():
    if os.path.exists(HISTORY_FILE):
        with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return []

def save_record(record):
    with _history_lock:
        history = load_history()
        history.append(record)
        with open(HISTORY_FILE, 'w', encoding='utf-8') as f:
            json.dump(history, f, ensure_ascii=False, indent=2)

# ── RAG answer ──
def rag_answer(question, top_k=5):
    t0 = time.time()
    provider = get_provider()
    results = search(question, top_k=top_k)

    # Build enriched context from fusion results
    enriched = results[0].get('_enriched_context', '') if results else ''
    if not enriched:
        parts = []
        for i, r in enumerate(results):
            parts.append('[doc' + str(i+1) + ': ' + r['source'] + ']\n' + r['text'])
        enriched = '\n\n'.join(parts)
    enriched = enriched[:3500]  # fit within 8192 context window

    prompt = f"""你是一个海事领域问答助手。请严格基于以下参考资料回答问题。
资料来自多个来源(检索文档、知识图谱、Wiki知识)，请综合利用。
如果资料中没有相关信息，请说明"资料中未找到相关信息"。
回答要具体，引用原文，标注来源 [来源: 文件名]。

{enriched}

## 问题

{question}

## 回答"""

    answer = provider.chat([{'role': 'user', 'content': prompt}])
    elapsed = time.time() - t0
    # Extract unified search metadata
    query_type = results[0].get('_query_type', 'unknown') if results else 'unknown'
    strategy = results[0].get('_strategy', 'unknown') if results else 'unknown'
    search_time = results[0].get('_total_time', 0) if results else 0
    return {
        'answer': answer,
        'sources': [r['source'] for r in results],
        'elapsed': round(elapsed, 1),
        'chunks_used': len(results),
        'mode': 'LambdaRAG',
        'query_type': query_type,
        'strategy': strategy,
        'search_time': search_time,
    }

# ── Wiki answer ──
def wiki_answer(question, top_k=5):
    t0 = time.time()
    provider = get_provider()
    from pathlib import Path

    wiki_path = Path(WIKI_DIR)
    wiki_pages = []
    for subdir in ['sources', 'entities', 'topics', 'analyses']:
        d = wiki_path / subdir
        if d.exists():
            for f in d.glob('*.md'):
                try:
                    content = f.read_text(encoding='utf-8')
                    q_chars = set(question)
                    match_score = sum(1 for c in q_chars if c in content)
                    if match_score > len(question) * 0.3:
                        wiki_pages.append({
                            'name': f.stem,
                            'content': content[:1500],
                            'score': match_score,
                        })
                except:
                    pass

    wiki_pages.sort(key=lambda x: x['score'], reverse=True)
    wiki_pages = wiki_pages[:3]

    if wiki_pages:
        wiki_context = '\n\n'.join([
            f'[Wiki: {p["name"]}]\n{p["content"]}' for p in wiki_pages
        ])
        source_type = 'wiki'
    else:
        results = search(question, top_k=top_k)
        compile_context = '\n\n'.join([
            f'[doc: {r["source"]}]\n{r["text"]}' for r in results
        ])
        compile_prompt = f"""请阅读以下文档片段，提炼出与问题相关的核心知识点。
用结构化的方式组织，标注来源。

文档:
{compile_context}

问题: {question}

请输出结构化的知识摘要:"""
        compiled = provider.chat([{'role': 'user', 'content': compile_prompt}])
        wiki_context = compiled
        source_type = 'compiled'

        safe_name = question[:30].replace('/', '_').replace(' ', '_')
        analysis_path = wiki_path / 'analyses' / f'{safe_name}.md'
        analysis_path.parent.mkdir(parents=True, exist_ok=True)
        with open(analysis_path, 'w', encoding='utf-8') as f:
            f.write(f'# {question}\n\n{compiled}\n')

    answer_prompt = f"""你是一个海事领域 wiki 知识库问答助手。
基于以下已编译的 wiki 知识回答问题。回答要具体，标注来源。

## Wiki 知识

{wiki_context}

## 问题

{question}

## 回答"""

    answer = provider.chat([{'role': 'user', 'content': answer_prompt}])
    elapsed = time.time() - t0
    return {
        'answer': answer,
        'source_type': source_type,
        'wiki_pages_used': len(wiki_pages),
        'elapsed': round(elapsed, 1),
        'mode': 'Wiki',
    }

# ── Async results store ──

# -- BM25 Baseline answer (for comparison) --
def bm25_baseline_answer(question, top_k=5):
    t0 = time.time()
    provider = get_provider()
    try:
        from search_engine import search as bm25_only
        results = bm25_only(question, top_k=top_k)
    except Exception:
        results = []
    context_parts = []
    for i, r in enumerate(results):
        context_parts.append('[doc' + str(i+1) + ': ' + r['source'] + ']' + chr(10) + r['text'])
    context = chr(10)*2 .join(context_parts)
    prompt = ('You are a maritime QA assistant. Answer based on docs only.' + chr(10)
        + '## Docs' + chr(10) + context + chr(10)
        + '## Question' + chr(10) + question + chr(10) + '## Answer')
    answer = provider.chat([{'role': 'user', 'content': prompt}])
    elapsed = time.time() - t0
    return {
        'answer': answer,
        'sources': [r['source'] for r in results],
        'elapsed': round(elapsed, 1),
        'chunks_used': len(results),
        'mode': 'BM25 Baseline',
    }

_results = {}

# ── API routes ──
@app.route('/api/ask', methods=['POST'])
def api_ask():
    data = request.json
    question = data.get('question', '').strip()
    if not question:
        return jsonify({'error': 'question is required'}), 400

    req_id = str(int(time.time() * 1000))
    _results[req_id] = {'rag': None, 'wiki': None, 'question': question}

    def run_rag():
        try:
            _results[req_id]['rag'] = rag_answer(question)
        except Exception as e:
            _results[req_id]['rag'] = {'answer': f'Error: {e}', 'elapsed': 0, 'mode': 'RAG'}

    def run_wiki():
        try:
            _results[req_id]['wiki'] = wiki_answer(question)
        except Exception as e:
            _results[req_id]['wiki'] = {'answer': f'Error: {e}', 'elapsed': 0, 'mode': 'Wiki'}

    def run_baseline():
        try:
            _results[req_id]['baseline'] = bm25_baseline_answer(question)
        except Exception as e:
            _results[req_id]['baseline'] = {'answer': 'Error: ' + str(e), 'elapsed': 0, 'mode': 'BM25 Baseline'}

    t1 = threading.Thread(target=run_rag)
    t2 = threading.Thread(target=run_wiki)
    t3 = threading.Thread(target=run_baseline)
    t1.start()
    t2.start()
    t3.start()

    return jsonify({'req_id': req_id, 'status': 'processing'})


@app.route('/api/result/<req_id>')
def api_result(req_id):
    if req_id not in _results:
        return jsonify({'error': 'not found'}), 404
    r = _results[req_id]
    done = r['rag'] is not None and r['wiki'] is not None

    # Save to history when both are done
    if done and not r.get('_saved'):
        r['_saved'] = True
        record = {
            'id': req_id,
            'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            'question': r['question'],
            'rag': r['rag'],
            'wiki': r['wiki'],
        }
        threading.Thread(target=save_record, args=(record,)).start()

    return jsonify({
        'status': 'done' if done else 'processing',
        'rag': r['rag'],
        'wiki': r['wiki'],
        'baseline': r.get('baseline'),
    })


@app.route('/api/history')
def api_history():
    history = load_history()
    history.reverse()  # newest first
    return jsonify(history[:50])  # last 50


@app.route('/api/history/export')
def api_history_export():
    history = load_history()
    return jsonify({
        'exported_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
        'total_records': len(history),
        'records': history,
    })


@app.route('/api/wiki/index')
def api_wiki_index():
    """List all wiki pages grouped by category"""
    from pathlib import Path
    wiki_path = Path(WIKI_DIR)
    result = {}
    for subdir in ['sources', 'entities', 'topics', 'analyses']:
        d = wiki_path / subdir
        pages = []
        if d.exists():
            for f in sorted(d.glob('*.md')):
                stat = f.stat()
                pages.append({
                    'name': f.stem,
                    'path': f'{subdir}/{f.name}',
                    'size': stat.st_size,
                    'modified': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M'),
                })
        result[subdir] = pages
    # Also read index.md
    idx_path = wiki_path / 'index.md'
    result['index_content'] = idx_path.read_text(encoding='utf-8') if idx_path.exists() else ''
    return jsonify(result)


@app.route('/api/wiki/page/<path:page_path>')
def api_wiki_page(page_path):
    """Read a single wiki page"""
    from pathlib import Path
    wiki_path = Path(WIKI_DIR)
    full_path = wiki_path / page_path
    if not full_path.exists() or not str(full_path).startswith(str(wiki_path)):
        return jsonify({'error': 'not found'}), 404
    content = full_path.read_text(encoding='utf-8')
    return jsonify({'path': page_path, 'content': content, 'size': len(content)})


@app.route('/api/rag/search')
def api_rag_search():
    """Search RAG chunks"""
    query = request.args.get('q', '').strip()
    top_k = int(request.args.get('k', 10))
    if not query:
        return jsonify({'error': 'q parameter required'}), 400
    results = search(query, top_k=top_k)
    return jsonify({'query': query, 'total_results': len(results), 'results': results})


@app.route('/api/rag/stats')
def api_rag_stats():
    """RAG index statistics"""
    import json as json_mod
    from pathlib import Path
    idx_path = Path(_knowledge.get('indexFile', KNOWLEDGE_BASE + '/rag_index.json'))
    if not idx_path.exists():
        return jsonify({'error': 'index not built'}), 404
    with open(idx_path) as f:
        data = json_mod.load(f)
    # Sample chunks for preview
    sample_chunks = data['chunks'][:20]
    for c in sample_chunks:
        pass  # show full text
    return jsonify({
        'total_chunks': data['total_chunks'],
        'total_files': data['total_files'],
        'chunk_size': data['chunk_size'],
        'keyword_index_size': data.get('keyword_index_size', 0),
        'sample_chunks': sample_chunks,
    })


@app.route('/wiki')
def wiki_page():
    return render_template_string(WIKI_HTML)


@app.route('/rag')
def rag_page():
    return render_template_string(RAG_HTML)


@app.route('/')
def index():
    return render_template_string(HTML_TEMPLATE)


HTML_TEMPLATE = r"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maritime QA Demo</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f0f2f5; color: #333; }
.header { background: linear-gradient(135deg, #1a3a5c 0%, #2d6aa0 100%); color: white; padding: 20px 30px; }
.header-inner { max-width: 1400px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; }
.header h1 { font-size: 22px; }
.header p { opacity: 0.8; font-size: 13px; margin-top: 4px; }
.header-stats { text-align: right; font-size: 13px; opacity: 0.8; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.input-area { background: white; border-radius: 12px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.input-row { display: flex; gap: 12px; }
.input-row input { flex: 1; padding: 12px 16px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; outline: none; transition: border-color 0.2s; }
.input-row input:focus { border-color: #2d6aa0; }
.btn { padding: 12px 28px; background: #2d6aa0; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; transition: background 0.2s; white-space: nowrap; }
.btn:hover { background: #1a5a8e; }
.btn:disabled { background: #aaa; cursor: not-allowed; }
.btn-sm { padding: 6px 14px; font-size: 13px; border-radius: 6px; }
.btn-outline { background: transparent; border: 1px solid #2d6aa0; color: #2d6aa0; }
.btn-outline:hover { background: #e0eef8; }
.quick-questions { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px; }
.quick-questions span { padding: 6px 12px; background: #f5f5f5; border-radius: 16px; font-size: 13px; cursor: pointer; transition: background 0.2s; border: 1px solid transparent; }
.quick-questions span:hover { background: #e0eef8; border-color: #c0d8f0; }
.compare-area { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.panel { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); min-height: 200px; }
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 2px solid #f0f0f0; }
.panel-title { font-size: 18px; font-weight: 600; }
.panel-title.rag { color: #2d6aa0; }
.panel-title.wiki { color: #7b2d8e; }
.panel-badge { padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
.badge-rag { background: #e0eef8; color: #2d6aa0; }
.badge-wiki { background: #f0e0f8; color: #7b2d8e; }
.panel-content { font-size: 15px; line-height: 1.8; white-space: pre-wrap; word-wrap: break-word; max-height: 600px; overflow-y: auto; }
.panel-meta { margin-top: 12px; padding-top: 12px; border-top: 1px solid #f0f0f0; font-size: 13px; color: #888; }
.loading { text-align: center; padding: 40px; color: #888; }
.spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid #e0e0e0; border-top-color: #2d6aa0; border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 8px; vertical-align: middle; }
@keyframes spin { to { transform: rotate(360deg); } }
.tabs { display: flex; gap: 0; margin-top: 20px; margin-bottom: 0; }
.tab { padding: 10px 20px; background: #e8e8e8; cursor: pointer; font-size: 14px; border-radius: 8px 8px 0 0; }
.tab.active { background: white; font-weight: 600; }
.tab-content { background: white; border-radius: 0 12px 12px 12px; padding: 16px 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); display: none; max-height: 400px; overflow-y: auto; }
.tab-content.active { display: block; }
.hist-item { padding: 12px 0; border-bottom: 1px solid #f5f5f5; cursor: pointer; }
.hist-item:hover { background: #fafafa; }
.hist-item:last-child { border-bottom: none; }
.hist-q { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
.hist-meta { font-size: 12px; color: #999; }
.empty-hint { text-align: center; color: #ccc; padding: 30px; }
@media (max-width: 768px) { .compare-area { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="header">
    <div class="header-inner">
        <div>
            <h1>Maritime QA Demo</h1>
            <p>RAG vs Wiki | 1326 docs | Qwen2.5:32B local</p>
        </div>
        <div class="header-stats">
            <div style="margin-bottom:6px;">
                <a href="/" style="color:white;text-decoration:none;margin-right:16px;font-weight:600;">QA Demo</a>
                <a href="/wiki" style="color:white;text-decoration:none;margin-right:16px;opacity:0.85;">Wiki Browser</a>
                <a href="/rag" style="color:white;text-decoration:none;opacity:0.85;">RAG Chunks</a>
            </div>
            <div id="statsLine">-</div>
        </div>
    </div>
</div>
<div class="container">
    <div class="input-area">
        <div class="input-row">
            <input type="text" id="question" placeholder="Please enter a maritime question..." autofocus>
            <button class="btn" id="askBtn" onclick="askQuestion()">Ask</button>
        </div>
        <div class="quick-questions">
            <span onclick="askDirect('船舶进出港报告制度的主要内容是什么？')">Ship entry/exit reporting system</span>
            <span onclick="askDirect('船员适任证书的申请条件有哪些？')">Crew competency certificates</span>
            <span onclick="askDirect('琼州海峡船舶定线制是什么时候开始施行的？')">Qiongzhou Strait routing system</span>
            <span onclick="askDirect('海上交通安全法与水污染防治法在船舶管理方面的规定有何异同？')">Maritime law comparison</span>
            <span onclick="askDirect('中国船员发展报告从2019到2023年有什么变化趋势？')">Crew development trends</span>
        </div>
    </div>

    <div class="compare-area">
        <div class="panel" id="ragPanel">
            <div class="panel-header">
                <span class="panel-title rag">LambdaRAG (Unified)</span>
                <span class="panel-badge badge-rag" id="ragBadge">Classify + Route + Retrieve</span>
            </div>
            <div class="panel-content" id="ragContent" style="white-space:pre-wrap;">
                <div class="empty-hint">Ask to see RAG response</div>
            </div>
            <div class="panel-meta" id="ragMeta"></div>
        </div>
        <div class="panel" id="wikiPanel">
            <div class="panel-header">
                <span class="panel-title wiki">Wiki Mode</span>
                <span class="panel-badge badge-wiki">Compile + Query</span>
            </div>
            <div class="panel-content" id="wikiContent" style="white-space:pre-wrap;">
                <div class="empty-hint">Ask to see Wiki response</div>
            </div>
            <div class="panel-meta" id="wikiMeta"></div>
        </div>
    </div>

    <div class="tabs">
        <div class="tab active" onclick="switchTab('history')">History</div>
        <div class="tab" onclick="switchTab('export')">Export</div>
    </div>
    <div class="tab-content active" id="tab-history">
        <div class="empty-hint" id="historyEmpty">No history yet</div>
        <div id="historyList"></div>
    </div>
    <div class="tab-content" id="tab-export">
        <p style="margin-bottom:12px;color:#666;">All records are saved on the server. Click to export.</p>
        <button class="btn btn-sm btn-outline" onclick="exportHistory()">Export JSON</button>
        <pre id="exportPreview" style="margin-top:12px;font-size:12px;max-height:300px;overflow:auto;background:#f8f8f8;padding:12px;border-radius:8px;display:none;"></pre>
    </div>
</div>

<script>
const qInput = document.getElementById('question');
const askBtn = document.getElementById('askBtn');

qInput.addEventListener('keydown', e => { if (e.key === 'Enter') askQuestion(); });

function askDirect(q) { qInput.value = q; askQuestion(); }

function askQuestion() {
    const q = qInput.value.trim();
    if (!q) return;
    askBtn.disabled = true;
    askBtn.textContent = 'Processing...';
    document.getElementById('ragContent').innerHTML = '<div class="loading"><span class="spinner"></span>RAG retrieving...</div>';
    document.getElementById('wikiContent').innerHTML = '<div class="loading"><span class="spinner"></span>Wiki compiling...</div>';
    document.getElementById('ragMeta').textContent = '';
    document.getElementById('wikiMeta').textContent = '';

    fetch('/api/ask', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({question: q})
    }).then(r => r.json()).then(data => pollResult(data.req_id, q));
}

function pollResult(reqId, question) {
    const poll = setInterval(() => {
        fetch('/api/result/' + reqId).then(r => r.json()).then(data => {
            if (data.rag) {
                document.getElementById("ragContent").textContent = data.rag.answer;
                const srcs = data.rag.sources ? data.rag.sources.slice(0,3).join(', ') : '';
                var qtype = data.rag.query_type || 'unknown';
                var strat = data.rag.strategy || 'unknown';
                var stime = data.rag.search_time || 0;
                document.getElementById('ragBadge').textContent = qtype + ' → ' + strat;
                document.getElementById('ragMeta').innerHTML =
                    '<b>Type:</b> ' + qtype + ' | <b>Strategy:</b> ' + strat +
                    '<br><b>Time:</b> ' + data.rag.elapsed + 's (search: ' + stime + 's) | <b>Chunks:</b> ' + (data.rag.chunks_used||0) +
                    '<br><b>Sources:</b> ' + srcs;
            }
            if (data.wiki) {
                document.getElementById("wikiContent").textContent = data.wiki.answer;
                document.getElementById('wikiMeta').innerHTML =
                    '<b>Time:</b> ' + data.wiki.elapsed + 's | <b>Source type:</b> ' + (data.wiki.source_type||'wiki') +
                    ' | <b>Wiki pages:</b> ' + (data.wiki.wiki_pages_used||0);
            }
            if (data.status === 'done') {
                clearInterval(poll);
                askBtn.disabled = false;
                askBtn.textContent = 'Ask';
                loadHistory();
            }
        });
    }, 2000);
}

function loadHistory() {
    fetch('/api/history').then(r => r.json()).then(data => {
        const el = document.getElementById('historyList');
        const empty = document.getElementById('historyEmpty');
        if (!data.length) { empty.style.display = 'block'; el.innerHTML = ''; return; }
        empty.style.display = 'none';
        document.getElementById('statsLine').textContent = 'Total records: ' + data.length;
        el.innerHTML = data.map((h, i) =>
            '<div class="hist-item" onclick="showRecord(' + i + ')">' +
            '<div class="hist-q">' + escHtml(h.question) + '</div>' +
            '<div class="hist-meta">' + h.timestamp +
            ' | RAG: ' + (h.rag?h.rag.elapsed:'-') + 's' +
            ' | Wiki: ' + (h.wiki?h.wiki.elapsed:'-') + 's</div></div>'
        ).join('');
        window._histData = data;
    });
}

function showRecord(idx) {
    const h = window._histData[idx];
    if (!h) return;
    qInput.value = h.question;
    if (h.rag) {
        document.getElementById("ragContent").textContent = h.rag.answer;
        const srcs = h.rag.sources ? h.rag.sources.slice(0,3).join(', ') : '';
        document.getElementById('ragMeta').innerHTML = '<b>Time:</b> ' + h.rag.elapsed + 's | <b>Sources:</b> ' + srcs;
    }
    if (h.wiki) {
        document.getElementById("wikiContent").textContent = h.wiki.answer;
        document.getElementById('wikiMeta').innerHTML = '<b>Time:</b> ' + h.wiki.elapsed + 's | <b>Source:</b> ' + (h.wiki.source_type||'wiki');
    }
    window.scrollTo({top: 0, behavior: 'smooth'});
}

function switchTab(name) {
    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
    document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
    event.target.classList.add('active');
    document.getElementById('tab-' + name).classList.add('active');
}

function exportHistory() {
    fetch('/api/history/export').then(r => r.json()).then(data => {
        const pre = document.getElementById('exportPreview');
        pre.style.display = 'block';
        pre.textContent = JSON.stringify(data, null, 2);
    });
}

function escHtml(s) {
    const d = document.createElement('div');
    d.textContent = s;
    return d.innerHTML;
}

function renderAnswer(text) {
    if (typeof marked === "undefined") return escHtml(text);
    var clean = text.replace(/([^\n])\n([^\n\-\#\*\|])/g, "$1\n\n$2");
    clean = clean.replace(/([^\n])\n(- )/g, "$1\n\n$2");
    clean = clean.replace(/([^\n])\n(#{1,3} )/g, "$1\n\n$2");
    try { return marked.parse(clean); } catch(e) { return "<pre>" + escHtml(text) + "</pre>"; }
}

// Load history on page load
loadHistory();
</script>
</body>
</html>
"""

WIKI_HTML = r"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wiki Browser</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: -apple-system,"PingFang SC","Microsoft YaHei",sans-serif; background:#f0f2f5; color:#333; }
.header { background:linear-gradient(135deg,#5b2d8e 0%,#7b4dae 100%); color:white; padding:16px 30px; }
.header-inner { max-width:1400px; margin:0 auto; display:flex; justify-content:space-between; align-items:center; }
.header h1 { font-size:22px; }
.header a { color:white; text-decoration:none; opacity:0.8; }
.header a:hover { opacity:1; }
.container { max-width:1400px; margin:0 auto; padding:20px; display:flex; gap:20px; }
.sidebar { width:300px; flex-shrink:0; }
.main { flex:1; min-width:0; }
.card { background:white; border-radius:12px; padding:16px; margin-bottom:16px; box-shadow:0 2px 8px rgba(0,0,0,0.08); }
.card h3 { font-size:15px; margin-bottom:10px; color:#5b2d8e; }
.page-list { max-height:250px; overflow-y:auto; }
.page-item { padding:6px 8px; cursor:pointer; font-size:13px; border-radius:6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.page-item:hover { background:#f0e0f8; }
.page-item.active { background:#e0d0f0; font-weight:600; }
.badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:11px; background:#f0e0f8; color:#5b2d8e; margin-left:6px; }
.content-area { font-size:15px; line-height:1.8; word-wrap:break-word; max-height:700px; overflow-y:auto; }
.content-area h1 { font-size:20px; margin:16px 0 8px; color:#333; border-bottom:1px solid #eee; padding-bottom:6px; }
.content-area h2 { font-size:17px; margin:14px 0 6px; color:#444; }
.content-area h3 { font-size:15px; margin:10px 0 4px; color:#555; }
.content-area ul, .content-area ol { padding-left:20px; margin:8px 0; }
.content-area li { margin:4px 0; }
.content-area p { margin:8px 0; }
.content-area code { background:#f0f0f0; padding:1px 4px; border-radius:3px; font-size:13px; }
.content-area blockquote { border-left:3px solid #ccc; padding-left:12px; color:#666; margin:8px 0; }
.stats-row { display:flex; gap:12px; margin-bottom:16px; }
.stat-box { flex:1; background:white; border-radius:10px; padding:14px; text-align:center; box-shadow:0 1px 4px rgba(0,0,0,0.06); }
.stat-num { font-size:28px; font-weight:700; color:#5b2d8e; }
.stat-label { font-size:12px; color:#888; margin-top:4px; }
.search-box { width:100%; padding:8px 12px; border:1px solid #ddd; border-radius:6px; font-size:14px; margin-bottom:10px; }
</style>
</head>
<body>
<div class="header">
    <div class="header-inner">
        <h1>Wiki Browser</h1>
        <div><a href="/">QA Demo</a> | <a href="/rag">RAG Chunks</a></div>
    </div>
</div>
<div class="container">
    <div class="sidebar">
        <div class="stats-row">
            <div class="stat-box"><div class="stat-num" id="nSources">-</div><div class="stat-label">Sources</div></div>
            <div class="stat-box"><div class="stat-num" id="nEntities">-</div><div class="stat-label">Entities</div></div>
            <div class="stat-box"><div class="stat-num" id="nTopics">-</div><div class="stat-label">Topics</div></div>
        </div>
        <div class="card"><h3>Sources</h3><input class="search-box" placeholder="Filter..." oninput="filterList('sources',this.value)"><div class="page-list" id="list-sources"></div></div>
        <div class="card"><h3>Entities</h3><input class="search-box" placeholder="Filter..." oninput="filterList('entities',this.value)"><div class="page-list" id="list-entities"></div></div>
        <div class="card"><h3>Topics</h3><div class="page-list" id="list-topics"></div></div>
        <div class="card"><h3>Analyses</h3><div class="page-list" id="list-analyses"></div></div>
    </div>
    <div class="main">
        <div class="card">
            <h3 id="pageTitle">Wiki Index</h3>
            <div class="content-area" id="pageContent">Loading...</div>
            <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
        </div>
    </div>
</div>
<script>
let wikiData = {};
function loadWiki() {
    fetch('/api/wiki/index').then(r=>r.json()).then(data => {
        wikiData = data;
        document.getElementById('nSources').textContent = data.sources.length;
        document.getElementById('nEntities').textContent = data.entities.length;
        document.getElementById('nTopics').textContent = data.topics.length;
        renderList('sources', data.sources);
        renderList('entities', data.entities);
        renderList('topics', data.topics);
        renderList('analyses', data.analyses);
        const idxContent = data.index_content || 'Wiki is empty. Run wiki_compile.py to populate.';
        if (typeof marked !== 'undefined') {
            document.getElementById('pageContent').innerHTML = marked.parse(idxContent);
        } else {
            document.getElementById('pageContent').textContent = idxContent;
        }
    });
}
function renderList(cat, pages) {
    const el = document.getElementById('list-' + cat);
    el.innerHTML = pages.map(p =>
        '<div class="page-item" data-path="' + p.path + '" data-name="' + p.name + '" onclick="loadPage(\'' + p.path + '\',\'' + escAttr(p.name) + '\')">' +
        p.name.substring(0, 50) + '<span class="badge">' + (p.size/1024).toFixed(1) + 'K</span></div>'
    ).join('');
}
function loadPage(path, name) {
    document.querySelectorAll('.page-item').forEach(e => e.classList.remove('active'));
    document.querySelectorAll('[data-path="'+path+'"]').forEach(e => e.classList.add('active'));
    document.getElementById('pageTitle').textContent = name;
    document.getElementById('pageContent').innerHTML = '<div style="color:#aaa">Loading...</div>';
    fetch('/api/wiki/page/' + path).then(r=>r.json()).then(data => {
        if (typeof marked !== 'undefined') {
            document.getElementById('pageContent').innerHTML = marked.parse(data.content);
        } else {
            document.getElementById('pageContent').textContent = data.content;
        }
    });
}
function filterList(cat, q) {
    const items = document.querySelectorAll('#list-' + cat + ' .page-item');
    q = q.toLowerCase();
    items.forEach(el => { el.style.display = el.dataset.name.toLowerCase().includes(q) ? '' : 'none'; });
}
function escAttr(s) { return s.replace(/'/g, "\\'"); }
loadWiki();
</script>
</body>
</html>
"""

RAG_HTML = r"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RAG Chunks Browser</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: -apple-system,"PingFang SC","Microsoft YaHei",sans-serif; background:#f0f2f5; color:#333; }
.header { background:linear-gradient(135deg,#1a3a5c 0%,#2d6aa0 100%); color:white; padding:16px 30px; }
.header-inner { max-width:1400px; margin:0 auto; display:flex; justify-content:space-between; align-items:center; }
.header h1 { font-size:22px; }
.header a { color:white; text-decoration:none; opacity:0.8; }
.header a:hover { opacity:1; }
.container { max-width:1400px; margin:0 auto; padding:20px; }
.stats-row { display:flex; gap:12px; margin-bottom:20px; }
.stat-box { flex:1; background:white; border-radius:10px; padding:16px; text-align:center; box-shadow:0 1px 4px rgba(0,0,0,0.06); }
.stat-num { font-size:32px; font-weight:700; color:#2d6aa0; }
.stat-label { font-size:12px; color:#888; margin-top:4px; }
.search-area { background:white; border-radius:12px; padding:20px; margin-bottom:20px; box-shadow:0 2px 8px rgba(0,0,0,0.08); }
.search-row { display:flex; gap:12px; }
.search-row input { flex:1; padding:12px 16px; border:2px solid #e0e0e0; border-radius:8px; font-size:16px; outline:none; }
.search-row input:focus { border-color:#2d6aa0; }
.btn { padding:12px 24px; background:#2d6aa0; color:white; border:none; border-radius:8px; font-size:15px; cursor:pointer; }
.btn:hover { background:#1a5a8e; }
.chunk-card { background:white; border-radius:10px; padding:16px; margin-bottom:12px; box-shadow:0 1px 4px rgba(0,0,0,0.06); border-left:4px solid #2d6aa0; }
.chunk-header { display:flex; justify-content:space-between; margin-bottom:8px; font-size:13px; color:#888; }
.chunk-source { font-weight:600; color:#2d6aa0; }
.chunk-score { background:#e0eef8; padding:2px 8px; border-radius:10px; font-size:12px; }
.chunk-text { font-size:14px; line-height:1.7; white-space:pre-wrap; word-wrap:break-word; max-height:200px; overflow-y:auto; background:#fafafa; padding:10px; border-radius:6px; }
.sample-title { font-size:16px; font-weight:600; margin:20px 0 12px; color:#555; }
</style>
</head>
<body>
<div class="header">
    <div class="header-inner">
        <h1>RAG Chunks Browser</h1>
        <div><a href="/">QA Demo</a> | <a href="/wiki">Wiki</a></div>
    </div>
</div>
<div class="container">
    <div class="stats-row">
        <div class="stat-box"><div class="stat-num" id="nChunks">-</div><div class="stat-label">Total Chunks</div></div>
        <div class="stat-box"><div class="stat-num" id="nFiles">-</div><div class="stat-label">Source Files</div></div>
        <div class="stat-box"><div class="stat-num" id="nChunkSize">-</div><div class="stat-label">Chunk Size</div></div>
        <div class="stat-box"><div class="stat-num" id="nKeywords">-</div><div class="stat-label">Keywords</div></div>
    </div>
    <div class="search-area">
        <div class="search-row">
            <input type="text" id="searchInput" placeholder="Search RAG chunks (e.g. ship entry/exit reporting)...">
            <select id="topK" style="padding:10px;border-radius:8px;border:1px solid #ddd;font-size:14px;">
                <option value="5">Top 5</option>
                <option value="10" selected>Top 10</option>
                <option value="20">Top 20</option>
                <option value="50">Top 50</option>
            </select>
            <button class="btn" onclick="doSearch()">Search</button>
        </div>
    </div>
    <div id="results"></div>
    <div class="sample-title" id="sampleTitle">Sample Chunks (from index)</div>
    <div id="sampleChunks"></div>
</div>
<script>
document.getElementById('searchInput').addEventListener('keydown', e => { if(e.key==='Enter') doSearch(); });

function loadStats() {
    fetch('/api/rag/stats').then(r=>r.json()).then(data => {
        document.getElementById('nChunks').textContent = data.total_chunks.toLocaleString();
        document.getElementById('nFiles').textContent = data.total_files.toLocaleString();
        document.getElementById('nChunkSize').textContent = data.chunk_size;
        document.getElementById('nKeywords').textContent = data.keyword_index_size.toLocaleString();
        const el = document.getElementById('sampleChunks');
        el.innerHTML = data.sample_chunks.map(c => renderChunk(c, null)).join('');
    });
}

function doSearch() {
    const q = document.getElementById('searchInput').value.trim();
    if (!q) return;
    const k = document.getElementById('topK').value;
    document.getElementById('results').innerHTML = '<div style="text-align:center;padding:20px;color:#888;">Searching...</div>';
    document.getElementById('sampleTitle').style.display = 'none';
    document.getElementById('sampleChunks').style.display = 'none';

    fetch('/api/rag/search?q=' + encodeURIComponent(q) + '&k=' + k)
    .then(r=>r.json()).then(data => {
        const el = document.getElementById('results');
        if (!data.results.length) {
            el.innerHTML = '<div style="text-align:center;padding:20px;color:#888;">No results found</div>';
            return;
        }
        el.innerHTML = '<div style="margin-bottom:12px;font-size:14px;color:#666;">Found ' + data.results.length + ' chunks for "<b>' + escHtml(q) + '</b>"</div>' +
            data.results.map(c => renderChunk(c, c.score)).join('');
    });
}

function renderChunk(c, score) {
    return '<div class="chunk-card">' +
        '<div class="chunk-header"><span class="chunk-source">' + escHtml(c.source) +
        ' #' + c.chunk_id + '</span>' +
        (score !== null ? '<span class="chunk-score">Score: ' + score + '</span>' : '') +
        '</div><div class="chunk-text">' + escHtml(c.text) + '</div></div>';
}

function escHtml(s) { const d=document.createElement('div'); d.textContent=s; return d.innerHTML; }
loadStats();
</script>
</body>
</html>
"""

if __name__ == '__main__':
    print("Loading search index...")
    search("test", top_k=1)  # warm up
    print("Index loaded. Starting server...")
    print("Access: http://0.0.0.0:8080")
    app.run(host='0.0.0.0', port=8080, debug=False, threaded=True)
