import { ref, nextTick, computed } from 'vue';
import { marked } from 'marked';
import { useChatStore } from '../store/chat';
import { getApiBase } from '../utils/apiBase';

interface MessagePayload {
    type: 'message';
    content: string;
    msgId?: string;
    attachments?: { type: string; mimeType: string; content: string; fileName: string }[];
    [key: string]: any;
}

function generateMsgId(): string {
    return crypto.randomUUID();
}

const renderer = new marked.Renderer();
renderer.link = ({ href, title, text }) => {
    const titleAttr = title ? ` title="${title}"` : '';
    return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
};

function ansiToHtml(text: string): string {
    const colorMap: Record<number, string> = {
        30: '#555', 31: '#e06c75', 32: '#98c379', 33: '#e5c07b',
        34: '#61afef', 35: '#c678dd', 36: '#56b6c2', 37: '#abb2bf',
    };
    let open = false, bold = false, dim = false;
    const result = text.replace(/\x1b\[([0-9;]*)m/g, (_match, codes: string) => {
        const parts = codes.split(';').map(Number);
        let out = '';
        for (const code of parts) {
            if (code === 0) {
                if (open)  { out += '</span>'; open = false; }
                if (bold)  { out += '</strong>'; bold = false; }
                if (dim)   { out += '</span>'; dim = false; }
            } else if (code === 1) {
                if (!bold) { out += '<strong>'; bold = true; }
            } else if (code === 2) {
                if (!dim)  { out += '<span style="opacity:0.45">'; dim = true; }
            } else if (colorMap[code]) {
                if (open)  { out += '</span>'; }
                out += `<span style="color:${colorMap[code]}">`;
                open = true;
            }
        }
        return out;
    });
    let tail = '';
    if (open) tail += '</span>';
    if (bold) tail += '</strong>';
    if (dim)  tail += '</span>';
    return result + tail;
}

const WORKSPACE_PATH_RE = /(workspace\/[^\s"'<>)]+\.(?:pdf|png|jpg|jpeg|gif|csv|json|txt|md|html|zip|mp3|wav|ogg|webm|m4a))/g;
const WORKSPACE_PREFIX = '';
const AUDIO_EXTENSIONS = new Set(['mp3', 'wav', 'ogg', 'webm', 'm4a']);

function linkifyWorkspaceFiles(html: string): string {
    return html.replace(WORKSPACE_PATH_RE, (match) => {
        const name = match.split('/').pop() || match;
        const absPath = match.startsWith('/') ? match : WORKSPACE_PREFIX + match;
        const ext = name.split('.').pop()?.toLowerCase() || '';
        if (AUDIO_EXTENSIONS.has(ext)) {
            return `<audio controls class="inline-audio" data-filepath="${absPath}" onplay="window.__hermesAudioSrc(this)"></audio>`;
        }
        return `<button class="file-download-link" data-filepath="${absPath}" data-filename="${name}" onclick="window.__hermesDownload(this)" title="Download ${name}">📎 ${name}</button>`;
    });
}

// Global audio source handler — sets src on first play via authenticated fetch
if (typeof window !== 'undefined' && !(window as any).__hermesAudioSrc) {
    (window as any).__hermesAudioSrc = (el: HTMLAudioElement) => {
        if (el.src) return; // already loaded
        const filepath = el.dataset.filepath;
        if (!filepath) return;
        const token = localStorage.getItem('nyx_session') || localStorage.getItem('titan_token') || '';
        const apiBase = getApiBase();
        el.src = `${apiBase}/api/files${filepath}?token=${encodeURIComponent(token)}`;
    };
}

// Global download handler — fetches file as blob and triggers download
if (typeof window !== 'undefined' && !(window as any).__hermesDownload) {
    (window as any).__hermesDownload = async (el: HTMLElement) => {
        const filepath = el.dataset.filepath;
        const filename = el.dataset.filename || 'download';
        if (!filepath) return;
        el.textContent = '⏳ ' + filename;
        try {
            const token = localStorage.getItem('nyx_session') || localStorage.getItem('titan_token') || '';
            const apiBase2 = getApiBase();
            const res = await fetch(`${apiBase2}/api/files${filepath}?token=${encodeURIComponent(token)}`);
            if (!res.ok) throw new Error(`${res.status}`);
            const blob = await res.blob();
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            a.click();
            URL.revokeObjectURL(url);
            el.textContent = '✅ ' + filename;
        } catch (err) {
            el.textContent = '❌ ' + filename;
            console.error('[download]', err);
        }
    };
}

export function parseMd(content: string | undefined): string {
    const raw = content || '';
    if (/\x1b\[/.test(raw)) {
        const escaped = raw.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        return `<pre style="font-family:var(--font-mono);line-height:1.5;white-space:pre-wrap">${ansiToHtml(escaped)}</pre>`;
    }
    let html = marked.parse(raw, { renderer, async: false, gfm: true, breaks: true }) as string;
    html = linkifyWorkspaceFiles(html);
    return html;
}

const DRAFT_KEY = 'chat_draft';
const HISTORY_KEY = 'chat_input_history';
const HISTORY_MAX = 50;

function loadDraft(): string {
    try { return sessionStorage.getItem(DRAFT_KEY) || ''; } catch { return ''; }
}
function saveDraft(v: string) {
    try { if (v) sessionStorage.setItem(DRAFT_KEY, v); else sessionStorage.removeItem(DRAFT_KEY); } catch {}
}
function loadHistory(): string[] {
    try { return JSON.parse(sessionStorage.getItem(HISTORY_KEY) || '[]'); } catch { return []; }
}
function pushHistory(v: string) {
    try {
        const h = loadHistory().filter(x => x !== v);
        h.unshift(v);
        sessionStorage.setItem(HISTORY_KEY, JSON.stringify(h.slice(0, HISTORY_MAX)));
    } catch {}
}

export function useMessages(wsSendFn: (payload: MessagePayload) => void) {
    const store = useChatStore();
    const sending = ref(false);
    const input = ref(loadDraft());
    const messagesEl = ref<HTMLElement | null>(null);
    let historyIdx = -1;

    function getViewport(): HTMLElement | null {
        const el = messagesEl.value;
        if (!el) return null;
        const root = (el as any).$el || el;
        return root.querySelector?.('[data-overlayscrollbars-viewport]') as HTMLElement || root;
    }

    function scrollToBottom(): void {
        // Double nextTick + rAF to ensure DOM has rendered after bulk message inserts
        nextTick(() => {
            nextTick(() => {
                requestAnimationFrame(() => {
                    const vp = getViewport();
                    if (vp) vp.scrollTop = vp.scrollHeight;
                });
            });
        });
    }

    function scrollIfAtBottom(): void {
        const vp = getViewport();
        if (!vp) return;
        if (vp.scrollHeight - vp.scrollTop - vp.clientHeight < 80) scrollToBottom();
    }

    // Persist draft on every keystroke (throttled via watch)
    let draftTimer: ReturnType<typeof setTimeout> | null = null;
    function onInputChange() {
        if (draftTimer) clearTimeout(draftTimer);
        draftTimer = setTimeout(() => saveDraft(input.value), 1000);
    }

    // Arrow-up/down history navigation — call from keydown handler
    function navigateHistory(dir: 'up' | 'down') {
        const history = loadHistory();
        if (!history.length) return;
        if (dir === 'up') {
            historyIdx = Math.min(historyIdx + 1, history.length - 1);
        } else {
            historyIdx = Math.max(historyIdx - 1, -1);
        }
        input.value = historyIdx === -1 ? '' : history[historyIdx];
    }

    let lastSentContent = '';
    function restoreLastSent() { input.value = lastSentContent; }

    async function send(attachmentPayload?: { type: string; mimeType: string; content: string; fileName: string }[]): Promise<void> {
        const hasText = input.value.trim().length > 0;
        const hasAttachments = attachmentPayload && attachmentPayload.length > 0;
        if ((!hasText && !hasAttachments) || sending.value) return;
        const content = input.value.trim();
        if (hasText) {
            lastSentContent = content;
            pushHistory(content);
        }
        historyIdx = -1;
        input.value = '';
        saveDraft('');
        sending.value = true;

        const msgId = generateMsgId();

        // Build local preview URLs for display
        const localAttachments = hasAttachments ? attachmentPayload!.map(a => {
            const mime = a.mimeType.split(';')[0];
            let dataUrl: string;
            if (a.mimeType.startsWith('audio/')) {
                const bytes = Uint8Array.from(atob(a.content), c => c.charCodeAt(0));
                dataUrl = URL.createObjectURL(new Blob([bytes], { type: mime }));
            } else {
                dataUrl = `data:${mime};base64,${a.content}`;
            }
            return { mimeType: a.mimeType, fileName: a.fileName, dataUrl };
        }) : undefined;

        const hasAudio = hasAttachments && attachmentPayload!.some(a => a.mimeType.startsWith('audio/'));

        // Always push optimistic local message with msgId (even for audio — will be patched with transcript)
        store.pushMessage({
            role: 'user' as const,
            content,
            agentId: null,
            msgId,
            attachments: localAttachments,
            pending: hasAudio, // audio messages are pending until transcript arrives
        });
        // Scroll handled by AgentsView.send() — don't scrollToBottom here

        const payload: MessagePayload = { type: 'message', content, msgId };
        if (hasAttachments) payload.attachments = attachmentPayload;
        wsSendFn(payload);
        sending.value = false;
    }

    return {
        sending, input, messagesEl,
        parseMd, scrollToBottom, scrollIfAtBottom, send, onInputChange, navigateHistory, restoreLastSent,
        startNewAssistantMessage: store.startNewAssistantMessage,
        appendAssistantMessage: store.appendAssistantDelta,
        finalizeAssistantMessage: store.finalizeAssistantMessage,
        resetAssistantMessageState: store.resetLocalSession,
        hasActiveStreamingMessage: store.hasActiveStreamingMessage,
        streamingMessageVisibleContent: computed(() => store.streamingMessageVisibleContent)
    };
}
