<?php
// templates/Pages/ollama_chat.php (example)
// Make sure you have a POST route: $builder->post('/ollama/chat','Ollama::chat');
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>AI Chat</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Bootstrap 5 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        :root { --chat-max-width: 800px; }
        body { background: #f7f7fb; }
        .chat-card {
            max-width: var(--chat-max-width);
            margin: 24px auto;
            border-radius: 1rem;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0,0,0,.06);
        }
        .chat-header {
            background: linear-gradient(135deg, #6f42c1, #8d6ed5);
            color: #fff;
        }
        .chat-body {
            height: 60vh;
            overflow-y: auto;
            background: #fff;
            padding: 16px;
        }
        .msg {
            display: flex;
            gap: .75rem;
            margin-bottom: .75rem;
            align-items: flex-end;
        }
        .msg.user { justify-content: flex-end; }
        .bubble {
            max-width: 100%;
            padding: .6rem .8rem;
            border-radius: 1rem;
            position: relative;
            display: inline-block;
            white-space: pre-wrap;
            overflow-wrap: anywhere;
            word-break: normal;
            box-shadow: 0 2px 8px rgba(0,0,0,.05);
        }

        .msg.user .bubble {
            background: #6f42c1; color: #fff;
            border-bottom-right-radius: .25rem;
        }
        .msg.bot .bubble {
            background: #f2effb; color: #2a2342;
            border-bottom-left-radius: .25rem;
        }
        .avatar {
            width: 34px; height: 34px; border-radius: 50%;
            display: inline-block; flex: 0 0 34px;
            background: #e9e6f7; color:#6f42c1; font-weight: 700;
            display:flex; align-items:center; justify-content:center;
        }
        .timestamp { font-size: .75rem; color: #9092a5; margin-top: .15rem; }
        .composer {
            background: #faf9ff;
            border-top: 1px solid #ece9fb;
        }
        .composer textarea {
            resize: none; min-height: 44px; max-height: 150px;
            border-radius: .75rem;
            flex: 1;
        }
        .btn-send {
            flex: 0 0 auto;
        }
        /* Typing dots */
        .typing {
            display:inline-flex; gap:6px; align-items:center;
            padding: .5rem .75rem;
            background:#f2effb; border-radius: 1rem;
        }
        .dot {
            width:6px; height:6px; background:#6f42c1; border-radius:50%;
            animation: pulse 1s infinite ease-in-out;
        }
        .dot:nth-child(2) { animation-delay: .15s; }
        .dot:nth-child(3) { animation-delay: .3s; }
        @keyframes pulse {
            0%, 80%, 100% { opacity:.2; transform: translateY(0); }
            40% { opacity:1; transform: translateY(-3px); }
        }
    </style>
</head>
<body>
<div class="card chat-card">
    <div class="chat-header p-3 d-flex align-items-center justify-content-between">
        <div class="d-flex align-items-center gap-2">
            <div class="avatar" style="background:#ffffff22;color:#fff">AI</div>
            <div>
                <div class="fw-semibold">Monash Assistant</div>
            </div>
        </div>
        <div class="d-flex align-items-center gap-2">
            <button class="btn btn-light btn-sm" id="btnClear">Clear</button>
        </div>
    </div>

    <div id="chatBody" class="chat-body">
        <!-- messages render here -->
    </div>

    <div class="composer p-3">
        <?= $this->Form->create(null, ['id' => 'ollamaForm', 'onsubmit' => 'return false;']) ?>
        <?= $this->Form->hidden('_csrfToken', ['value' => $this->request->getAttribute('csrfToken')]) ?>
        <div class="d-flex gap-2">
            <textarea id="ollamaPrompt" name="prompt" class="form-control" placeholder="Write a message... (Enter to send, Shift+Enter for new line)"></textarea>
            <button type="button" id="ollamaSend" class="btn btn-primary btn-send px-3">
                <span class="d-none d-sm-inline">Send</span> ➤
            </button>
        </div>
        <?= $this->Form->end() ?>
    </div>
</div>

<!-- Toast (errors) -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1080">
    <div id="toast" class="toast text-bg-danger" role="alert" data-bs-delay="4000">
        <div class="d-flex">
            <div class="toast-body" id="toastMsg">Something went wrong</div>
            <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
        </div>
    </div>
</div>

<script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
    (function(){
        const $chat = $('#chatBody');
        const $input = $('#ollamaPrompt');
        const $send = $('#ollamaSend');
        const $form = $('#ollamaForm');
        const toast = new bootstrap.Toast(document.getElementById('toast'));
        const $toastMsg = $('#toastMsg');
        const CSRF = $form.find('input[name="_csrfToken"]').val();
        const API = '<?= $this->Url->build("/ollama/chat") ?>';
        const STORAGE_KEY = 'ollamaChatHistory_v1';

        // ----- state & persistence -----
        function now() { return new Date(); }
        function fmt(ts){
            try { return new Date(ts).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}); }
            catch { return ''; }
        }
        function loadHistory(){
            try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
            catch { return []; }
        }
        function saveHistory(items){
            try { localStorage.setItem(STORAGE_KEY, JSON.stringify(items.slice(-200))); } catch(e){}
        }

        // Replace **bold** or __bold__ with <strong>bold</strong>
        function applyBold(text) {
        return text.replace(/(\*\*|__)(.*?)\1/g, '<strong>$2</strong>');
        }

        let history = loadHistory();

        function msgHTML(role, text, ts){
            const isUser = role === 'user';
            const avatar = isUser ? '<div class="avatar" style="background:#e8def8;color:#6f42c1">U</div>'
                : '<div class="avatar">AI</div>';
            return `
          <div class="msg ${isUser ? 'user' : 'bot'}">
            ${isUser ? '' : avatar}
            <div>
              <div class="bubble ${isUser ? '' : ''}">${renderWithLinks(text)}</div>
              <div class="timestamp">${fmt(ts)}</div>
            </div>
            ${isUser ? avatar : ''}
          </div>`;
        }
        function renderAll(){
            $chat.empty();
            history.forEach(m => $chat.append(msgHTML(m.role, m.text, m.ts)));
            scrollToBottom();
        }
        function addMessage(role, text){
            const m = { role, text, ts: now().toISOString() };
            history.push(m);
            saveHistory(history);
            $chat.append(msgHTML(role, text, m.ts));
            scrollToBottom();
        }
        function showTyping(){
            const el = $(`
          <div class="msg bot" id="typingRow">
            <div class="avatar">AI</div>
            <div class="typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>
          </div>
        `);
            $chat.append(el);
            scrollToBottom();
        }
        function hideTyping(){ $('#typingRow').remove(); }
        function scrollToBottom(){ $chat.stop().animate({scrollTop: $chat[0].scrollHeight}, 250); }
        function escapeHTML(s){ return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
        function escapeAttr(s){ return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
        // Escape HTML but preserve <strong> tags that we generate via applyBold().
        // This prevents showing "<strong>..." as text while still blocking any other HTML.
        function escapeHTMLAllowingBold(s) {
            const OPEN = '\u0001B\u0001';
            const CLOSE = '\u0001E\u0001';
            let t = String(s).replace(/<strong>/g, OPEN).replace(/<\/strong>/g, CLOSE);
            t = escapeHTML(t);
            return t.split(OPEN).join('<strong>').split(CLOSE).join('</strong>');
        }

        // Linkify plain URLs inside a text segment (no HTML), escaping surrounding text.
        function linkifyPlain(text) {
            const urlRe = /(https?:\/\/[^\s<>")']+[\w\/)\-_%#?=&]*)(?![^<]*>)/g;
            let out = '';
            let last = 0;
            let m;
            while ((m = urlRe.exec(text)) !== null) {
                const before = text.slice(last, m.index);
                if (before) out += escapeHTMLAllowingBold(before);
                let url = m[1];
                // Trim common trailing punctuation
                const trimmed = url.replace(/[),.;!?]+$/,'');
                // If the previous characters were "](", drop the stray "(" from half-finished Markdown links
                if (out.endsWith("](")) {
                    out = out.slice(0, -1);
                }
                out += `<a href="${escapeAttr(trimmed)}" target="_blank" rel="noopener">${escapeHTML(trimmed)}</a>`;
                last = m.index + m[0].length;
            }
            const after = text.slice(last);
            if (after) out += escapeHTMLAllowingBold(after);
            return out;
        }

        // Convert Markdown-style links [label](url) and then linkify remaining plain URLs.
        function renderWithLinks(text) {

            text = applyBold(text);

            const mdRe = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g;
            let out = '';
            let last = 0;
            let m;
            while ((m = mdRe.exec(text)) !== null) {
                const before = text.slice(last, m.index);
                if (before) out += linkifyPlain(before);
                let label = m[1];
                let url = m[2].replace(/[),.;!?]+$/,'');
                // Preserve any bold in the label while escaping everything else
                out += `<a href="${escapeAttr(url)}" target="_blank" rel="noopener">${escapeHTMLAllowingBold(label)}</a>`;
                last = m.index + m[0].length;
            }
            const rest = text.slice(last);
            if (rest) out += linkifyPlain(rest);
            return out;
        }

        // ----- input helpers -----
        function autosize(){
            $input.css('height','auto');
            const h = Math.min($input.prop('scrollHeight'), 150);
            $input.css('height', h + 'px');
        }
        $input.on('input', autosize);

        // Enter to send; Shift+Enter for newline
        $input.on('keydown', function(e){
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                $send.click();
            }
        });

        function buildHistoryPayload(maxTurns = 6) {
        // convert our UI roles to API roles and keep the last N messages
        return history
            .slice(-maxTurns)
            .map(m => ({
            role: (m.role === 'bot' ? 'assistant' : 'user'),
            content: m.text
            }));
        }

        // Clear chat
        $('#btnClear').on('click', function(){
            history = [];
            saveHistory(history);
            renderAll();
            $input.val('').trigger('input');
        });

        // ----- send flow -----
        function sendPrompt(){
            const prompt = $input.val().trim();
            if (!prompt) return;
            $send.prop('disabled', true);
            addMessage('user', prompt);
            $input.val('').trigger('input');

            showTyping();

            $.ajax({
                url: API,
                method: 'POST',
                timeout: 300000,
                data: {
                    prompt: prompt,
                    _csrfToken: CSRF,
                    // NEW: send history[]
                    'history': buildHistoryPayload(6)
                },
                headers: { 'X-CSRF-Token': CSRF },
                success: function (data) {
                    hideTyping();
                    const text = data?.text ?? data?.response?.response ?? '';
                    if (!text) {
                        addMessage('bot', '(no reply)');
                    } else {
                        addMessage('bot', text);
                    }
                },
                error: function (xhr) {
                    hideTyping();
                    const j = xhr.responseJSON;
                    const msg = j?.exception || j?.error || (xhr.status + ' ' + xhr.statusText) || 'Request failed';
                    addMessage('bot', '⚠️ ' + msg);
                    $toastMsg.text(msg);
                    try { toast.show(); } catch(e){}
                },
                complete: function(){
                    $send.prop('disabled', false);
                }
            });
        }

        $send.on('click', sendPrompt);
        $('#ollamaForm').on('submit', function(e){ e.preventDefault(); sendPrompt(); });

        // initial render
        renderAll();
        autosize();
    })();
</script>
</body>
</html>
