// retoor <retoor@molodetz.nl>

import { MessagesSocket } from "./MessagesSocket.js";
import { contentRenderer } from "./ContentRenderer.js";

const TYPING_THROTTLE_MS = 1500;
const TYPING_HIDE_MS = 4000;

export class MessagesLayout {
    constructor() {
        this.layout = document.querySelector(".messages-layout");
        if (!this.layout) {
            return;
        }
        this.thread = document.querySelector(".messages-thread");
        this.form = document.querySelector(".messages-input-area");
        this.input = this.form ? this.form.querySelector('input[name="content"]') : null;
        this.upload = this.form ? this.form.querySelector("dp-upload") : null;
        this.sendBtn = this.form ? this.form.querySelector(".messages-send-btn") : null;
        this._uploading = false;
        this._pendingSends = new Set();
        this.presenceEl = document.getElementById("messages-presence");
        this.typingEl = document.getElementById("typing-indicator");

        this.selfUid = this.layout.dataset.selfUid || "";
        this.otherUid = this.layout.dataset.otherUid || "";

        this._lastTypingSent = 0;
        this._typingHideTimer = null;
        this._socketReady = false;

        this.scrollThreadToEnd();
        if (this.input) {
            this.input.focus({ preventScroll: true });
        }

        this.initPresence();
        this.connect();
        this.bindForm();
        this.bindTyping();
        this.bindViewport();
        this.markRead();
    }

    bindViewport() {
        const viewport = window.visualViewport;
        if (!viewport || !this.input) {
            return;
        }
        const applyInset = () => {
            const inset = Math.max(0, window.innerHeight - viewport.height - viewport.offsetTop);
            this.layout.style.setProperty("--kb-inset", `${Math.round(inset)}px`);
            this.scrollThreadToEnd();
        };
        viewport.addEventListener("resize", applyInset);
        viewport.addEventListener("scroll", applyInset);
        this.input.addEventListener("focus", () => {
            window.setTimeout(() => {
                this.scrollThreadToEnd();
                this.input.scrollIntoView({ block: "end" });
            }, 250);
        });
        this.input.addEventListener("blur", () => {
            this.layout.style.setProperty("--kb-inset", "0px");
        });
    }

    connect() {
        this.socket = new MessagesSocket({
            onReady: () => this.onReady(),
            onMessage: (frame) => this.onFrame(frame),
            onClose: () => { this._socketReady = false; },
        });
        this.socket.connect();
    }

    onReady() {
        this._socketReady = true;
        if (this.otherUid) {
            this.socket.send({ type: "presence", with_uid: this.otherUid });
            this.markRead();
        }
    }

    onFrame(frame) {
        switch (frame.type) {
            case "message":
                this.handleIncoming(frame);
                break;
            case "typing":
                if (frame.from_uid === this.otherUid) this.showTyping();
                break;
            case "read":
                if (frame.by_uid === this.otherUid) this.markReceipts();
                break;
            case "presence":
                if (frame.user_uid === this.otherUid) {
                    this.renderPresence(frame.online, frame.last_seen);
                }
                break;
            default:
                break;
        }
    }

    bindForm() {
        if (!this.form || !this.input) return;
        this.form.addEventListener("submit", (event) => {
            if (!this._socketReady || !this.socket.isOpen()) {
                return;
            }
            event.preventDefault();
            this.sendViaSocket();
        });
        if (this.upload) {
            this.upload.addEventListener("dp-upload:busy", (event) => {
                this._uploading = !!(event.detail && event.detail.busy);
                this.refreshSendButton();
            });
        }
    }

    refreshSendButton() {
        if (!this.sendBtn) return;
        const busy = this._uploading || this._pendingSends.size > 0;
        this.sendBtn.disabled = busy;
        this.sendBtn.classList.toggle("is-sending", busy);
    }

    sendViaSocket() {
        const content = (this.input.value || "").trim();
        const attachmentUids = this.collectAttachments();
        if (!content && attachmentUids.length === 0) return;
        const clientId = "c" + Date.now() + Math.random().toString(36).slice(2, 8);
        this.appendOptimistic(content, clientId, attachmentUids.length);
        const ok = this.socket.send({
            type: "send",
            receiver_uid: this.otherUid,
            content,
            attachment_uids: attachmentUids,
            client_id: clientId,
        });
        if (!ok) {
            this.form.submit();
            return;
        }
        this._pendingSends.add(clientId);
        this.refreshSendButton();
        window.setTimeout(() => this.clearPendingSend(clientId), 8000);
        this.input.value = "";
        if (this.upload && typeof this.upload.clear === "function") {
            this.upload.clear();
        }
        this.input.focus({ preventScroll: true });
    }

    clearPendingSend(clientId) {
        if (this._pendingSends.delete(clientId)) {
            this.refreshSendButton();
        }
    }

    collectAttachments() {
        const hidden = this.form.querySelector('input[name="attachment_uids"]');
        if (!hidden || !hidden.value) return [];
        return hidden.value.split(",").map((v) => v.trim()).filter(Boolean);
    }

    bindTyping() {
        if (!this.input) return;
        this.input.addEventListener("input", () => {
            if (!this._socketReady || !this.otherUid) return;
            const now = Date.now();
            if (now - this._lastTypingSent < TYPING_THROTTLE_MS) return;
            this._lastTypingSent = now;
            this.socket.send({ type: "typing", receiver_uid: this.otherUid });
        });
    }

    appendOptimistic(content, clientId, attachmentCount) {
        const bubble = this.buildBubble({
            content,
            mine: true,
            clientId,
            time: "now",
            iso: new Date().toISOString(),
            attachmentCount,
        });
        bubble.classList.add("pending");
        this.insertBubble(bubble);
    }

    handleIncoming(frame) {
        if (frame.sender_uid === this.selfUid && frame.client_id) {
            this.clearPendingSend(frame.client_id);
            const pending = this.thread.querySelector(`.message-bubble[data-client-id="${frame.client_id}"]`);
            if (pending) {
                pending.classList.remove("pending");
                pending.dataset.msgUid = frame.uid;
                const oldBody = pending.querySelector(".rendered-content");
                if (oldBody && frame.content !== undefined) {
                    const body = document.createElement("div");
                    body.className = "rendered-content";
                    body.setAttribute("data-render", "");
                    body.textContent = frame.content || "";
                    oldBody.replaceWith(body);
                    this.renderBody(body);
                }
                const time = pending.querySelector(".message-time");
                if (time && frame.created_at) {
                    time.setAttribute("datetime", frame.created_at);
                    time.dataset.dt = "";
                    time.dataset.dtMode = "ago";
                    if (window.app && window.app.localTime) window.app.localTime.apply(time);
                    else time.textContent = frame.time_ago;
                } else if (time) {
                    time.textContent = frame.time_ago;
                }
                const placeholder = pending.querySelector(".attachment-pending");
                if (placeholder) placeholder.remove();
                const gallery = this.renderAttachments(frame.attachments);
                if (gallery) pending.insertBefore(gallery, time || null);
                this.bumpConversation(frame, true);
                return;
            }
        }

        const partnerUid = frame.sender_uid === this.selfUid ? frame.receiver_uid : frame.sender_uid;
        const inThisThread = this.otherUid && partnerUid === this.otherUid;

        if (inThisThread) {
            const mine = frame.sender_uid === this.selfUid;
            const bubble = this.buildBubble({
                content: frame.content,
                mine,
                time: frame.time_ago,
                iso: frame.created_at,
                uid: frame.uid,
                attachments: frame.attachments,
            });
            this.insertBubble(bubble);
            if (!mine && this._socketReady) {
                this.socket.send({ type: "read", with_uid: this.otherUid });
            }
        }
        this.bumpConversation(frame, frame.sender_uid === this.selfUid);
    }

    buildBubble({ content, mine, time, iso, uid, clientId, attachments, attachmentCount }) {
        const bubble = document.createElement("div");
        bubble.className = "message-bubble " + (mine ? "mine" : "theirs");
        if (uid) bubble.dataset.msgUid = uid;
        if (clientId) bubble.dataset.clientId = clientId;

        const body = document.createElement("div");
        body.className = "rendered-content";
        body.setAttribute("data-render", "");
        body.textContent = content || "";
        bubble.appendChild(body);

        const gallery = this.renderAttachments(attachments);
        if (gallery) {
            bubble.appendChild(gallery);
        } else if (attachmentCount > 0) {
            const placeholder = document.createElement("div");
            placeholder.className = "attachment-pending";
            placeholder.textContent = attachmentCount === 1
                ? "Uploading attachment..."
                : `Uploading ${attachmentCount} attachments...`;
            bubble.appendChild(placeholder);
        }

        const timeEl = document.createElement(iso ? "time" : "span");
        timeEl.className = "message-time";
        if (iso) {
            timeEl.setAttribute("datetime", iso);
            timeEl.dataset.dt = "";
            timeEl.dataset.dtMode = "ago";
            if (window.app && window.app.localTime) window.app.localTime.apply(timeEl);
            else timeEl.textContent = time || "";
        } else {
            timeEl.textContent = time || "";
        }
        bubble.appendChild(timeEl);

        if (mine) {
            const receipt = document.createElement("span");
            receipt.className = "message-receipt";
            receipt.hidden = true;
            receipt.innerHTML = "&#x2713;&#x2713;";
            bubble.appendChild(receipt);
        }

        this.renderBody(body);
        return bubble;
    }

    renderBody(el) {
        contentRenderer.applyTo(el);
        el.querySelectorAll("img:not(.avatar-img)").forEach((img) => {
            img.dataset.lightbox = "";
        });
        contentRenderer.highlightAll();
    }

    renderAttachments(attachments) {
        if (!attachments || !attachments.length) return null;
        const gallery = document.createElement("div");
        gallery.className = "attachment-gallery";
        for (const att of attachments) {
            const item = document.createElement("div");
            item.className = "attachment-gallery-item";
            if (att.is_image) {
                const img = document.createElement("img");
                img.src = att.thumbnail_url || att.url;
                img.alt = att.original_filename || "";
                img.loading = "lazy";
                img.className = "gallery-thumb";
                img.dataset.lightbox = "";
                img.dataset.full = att.url;
                if (att.mime_type) img.dataset.mime = att.mime_type;
                item.appendChild(img);
            } else if (att.is_video) {
                const video = document.createElement("video");
                video.src = att.url;
                video.controls = true;
                video.preload = "metadata";
                video.className = "gallery-video";
                item.appendChild(video);
            } else {
                const link = document.createElement("a");
                link.href = att.url;
                link.target = "_blank";
                link.rel = "noopener";
                link.className = "non-image";
                link.download = att.original_filename || "file";
                link.textContent = att.original_filename || "file";
                item.appendChild(link);
            }
            gallery.appendChild(item);
        }
        return gallery;
    }

    insertBubble(bubble) {
        if (!this.thread) return;
        if (this.typingEl && this.typingEl.parentElement === this.thread) {
            this.thread.insertBefore(bubble, this.typingEl);
        } else {
            this.thread.appendChild(bubble);
        }
        this.scrollThreadToEnd();
    }

    markReceipts() {
        this.thread.querySelectorAll(".message-bubble.mine .message-receipt").forEach((el) => {
            el.hidden = false;
        });
    }

    markRead() {
        if (this._socketReady && this.otherUid) {
            this.socket.send({ type: "read", with_uid: this.otherUid });
        }
    }

    showTyping() {
        if (!this.typingEl) return;
        this.typingEl.hidden = false;
        this.scrollThreadToEnd();
        clearTimeout(this._typingHideTimer);
        this._typingHideTimer = setTimeout(() => {
            this.typingEl.hidden = true;
        }, TYPING_HIDE_MS);
    }

    initPresence() {
        if (!this.presenceEl) return;
        const online = this.layout.dataset.otherOnline === "1";
        const lastSeen = this.layout.dataset.otherLastSeen || null;
        this.renderPresence(online, lastSeen);
    }

    renderPresence(online, lastSeen) {
        if (!this.presenceEl) return;
        this.presenceEl.classList.toggle("online", !!online);
        if (online) {
            this.presenceEl.textContent = "online";
        } else if (lastSeen) {
            this.presenceEl.textContent = "last seen " + this.formatLastSeen(lastSeen);
        } else {
            this.presenceEl.textContent = "offline";
        }
    }

    formatLastSeen(iso) {
        const then = new Date(iso);
        if (Number.isNaN(then.getTime())) return "recently";
        const seconds = Math.max(0, Math.floor((Date.now() - then.getTime()) / 1000));
        if (seconds < 60) return "just now";
        if (seconds < 3600) return Math.floor(seconds / 60) + "m ago";
        if (seconds < 86400) return Math.floor(seconds / 3600) + "h ago";
        return then.toLocaleDateString("en-GB");
    }

    bumpConversation(frame, mine) {
        const partnerUid = mine ? frame.receiver_uid : frame.sender_uid;
        const item = document.querySelector(`.conversation-item[data-conv-uid="${partnerUid}"]`);
        if (!item) return;
        const preview = item.querySelector(".conversation-preview");
        if (preview) preview.textContent = frame.content.slice(0, 60);
        const dot = item.querySelector(".conversation-unread-dot");
        if (dot) {
            dot.hidden = mine || partnerUid === this.otherUid;
        }
        const list = item.parentElement;
        if (list && list.firstElementChild !== item) {
            list.insertBefore(item, list.firstElementChild);
        }
    }

    scrollThreadToEnd() {
        if (this.thread) {
            this.thread.scrollTop = this.thread.scrollHeight;
        }
    }
}
