// retoor <retoor@molodetz.nl>

import { Component } from "./Component.js";
import { assetUrl } from "../assetVersion.js";
import DeviiSocket from "../devii/DeviiSocket.js";
import Markdown from "../devii/markdown.js";

const DOCS_CHAT_CSS_ID = "docs-chat-css";
const CHANNEL = "docs";

export class AppDocsChat extends Component {
    constructor() {
        super();
        this.markdown = new Markdown();
        this.socket = null;
        this._seeded = false;
        this._bootstrapped = false;
        this._pending = {};
        this._lastHistoryQuestion = null;
    }

    _ensureCss() {
        if (document.getElementById(DOCS_CHAT_CSS_ID)) return;
        const link = document.createElement("link");
        link.id = DOCS_CHAT_CSS_ID;
        link.rel = "stylesheet";
        link.href = assetUrl("/static/css/docs-chat.css");
        document.head.appendChild(link);
    }

    connectedCallback() {
        if (this._built) return;
        this._built = true;
        this._build();
        this._bindSidebar();
        this._start();
    }

    disconnectedCallback() {
        if (this.socket) this.socket.close();
    }

    _build() {
        this.innerHTML = "";
        this.log = document.createElement("div");
        this.log.className = "docs-chat-log";
        this.log.setAttribute("role", "log");
        this.log.setAttribute("aria-live", "polite");

        this.status = document.createElement("div");
        this.status.className = "docs-chat-status";
        this.status.hidden = true;

        this.form = document.createElement("form");
        this.form.className = "docs-chat-input";
        this.field = document.createElement("textarea");
        this.field.className = "docs-chat-field";
        this.field.rows = 1;
        this.field.placeholder = "Ask a question about DevPlace...";
        this.field.setAttribute("aria-label", "Ask the documentation");
        this.send = document.createElement("button");
        this.send.type = "submit";
        this.send.className = "docs-chat-send";
        this.send.textContent = "Ask";
        this.form.append(this.field, this.send);

        this.append(this.log, this.status, this.form);

        this.form.addEventListener("submit", (event) => {
            event.preventDefault();
            this._submit(this.field.value);
        });
        this.field.addEventListener("keydown", (event) => {
            if (event.key === "Enter" && !event.shiftKey) {
                event.preventDefault();
                this._submit(this.field.value);
            }
        });
        this.field.addEventListener("input", () => this._autosize());
        this.log.addEventListener("click", (event) => this._onLogClick(event));
    }

    _bindSidebar() {
        const search = document.querySelector("form.docs-search-form");
        if (!search) return;
        const input = search.querySelector("input[name='q']");
        search.addEventListener("submit", (event) => {
            event.preventDefault();
            const value = input ? input.value : "";
            this._submit(value);
            if (input) input.value = "";
        });
    }

    async _start() {
        try {
            await fetch("/devii/session", { credentials: "same-origin" });
        } catch (error) {
        }
        this.socket = new DeviiSocket(
            {
                onReady: () => this._onReady(),
                onMessage: (message) => this._onMessage(message),
                onClose: () => this._setStatus("Disconnected. Reconnecting..."),
                onError: () => this._setStatus("Connection error."),
            },
            CHANNEL,
        );
        this.socket.connect();
    }

    _onReady() {
        this._setStatus("");
    }

    _onMessage(message) {
        const kind = message.type;
        if (kind === "reply") {
            this._resolvePending();
            this._agent(message.text);
            if (!this._bootstrapped) {
                this._bootstrapped = true;
                this._maybeSeed();
            }
            this._setStatus("");
        } else if (kind === "history") {
            this._renderHistory(message.messages);
            this._bootstrapped = true;
            this._maybeSeed();
        } else if (kind === "clear") {
            this.log.innerHTML = "";
        } else if (kind === "topic") {
            this._topic(message);
        } else if (kind === "error") {
            this._resolvePending();
            this._errorLine(message.text);
            this._setStatus("");
        } else if (kind === "status") {
            this._setStatus(message.text);
        } else if (kind === "trace") {
            this._trace(message);
        } else if (kind === "task") {
            this._setStatus("Working...");
        } else if (kind === "avatar" && message.id) {
            this.socket.send({ type: "avatar_result", id: message.id, result: {} });
        } else if (kind === "client" && message.id) {
            this.socket.send({ type: "client_result", id: message.id, result: {} });
        }
    }

    _maybeSeed() {
        if (this._seeded) return;
        this._seeded = true;
        const seed = this.attr("seed").trim();
        if (!seed) return;
        if (seed === this._lastHistoryQuestion) return;
        this._submit(seed);
    }

    _submit(raw) {
        const text = (raw || "").trim();
        if (!text) return;
        this._pending = {};
        this._lastQuestion = text;
        this._userLine(text);
        this.field.value = "";
        this._autosize();
        if (this.socket && this.socket.send({ type: "input", text })) {
            this._setStatus("Docii is searching the docs");
        } else {
            this._errorLine("Not connected.");
        }
    }

    _topic(message) {
        const reason = (message.reason || "").trim();
        if (message.decision === "new") {
            this.log.innerHTML = "";
            this._pending = {};
            if (this._lastQuestion) this._userLine(this._lastQuestion);
            this._noteLine(
                reason ? `New topic - ${reason}` : "New topic - starting fresh",
                "new",
            );
        } else {
            this._noteLine(reason ? `Follow-up - ${reason}` : "Follow-up", "follow");
        }
    }

    _noteLine(text, kind) {
        const row = document.createElement("div");
        row.className = `docs-chat-note ${kind}`;
        row.textContent = text;
        this._append(row);
    }

    _trace(message) {
        const event = message.event;
        const name = message.name || "";
        const detail = message.detail || "";
        if (event === "call") {
            const row = this._stepRow(name, detail);
            this._append(row);
            (this._pending[name] = this._pending[name] || []).push(row);
            this._setStatus("");
        } else if (event === "ok" || event === "err") {
            const queue = this._pending[name];
            const row = queue && queue.shift();
            if (row) {
                row.classList.remove("pending");
                row.classList.add(event === "ok" ? "done" : "error");
            }
        } else {
            this._setStatus(this._eventLabel(event));
        }
    }

    _resolvePending() {
        Object.values(this._pending).forEach((queue) => {
            (queue || []).forEach((row) => {
                row.classList.remove("pending");
                row.classList.add("done");
            });
        });
        this._pending = {};
    }

    _stepRow(name, detail) {
        const row = document.createElement("div");
        row.className = "docs-chat-step pending";
        const icon = document.createElement("span");
        icon.className = "docs-chat-step-icon";
        const label = document.createElement("span");
        label.className = "docs-chat-step-label";
        label.textContent = this._stepLabel(name, detail);
        row.append(icon, label);
        return row;
    }

    _stepLabel(name, detail) {
        if (name === "search_docs") {
            return detail ? `Searching the docs: ${detail}` : "Searching the docs";
        }
        return detail ? `${name}: ${detail}` : name;
    }

    _eventLabel(event) {
        const labels = {
            compact: "Condensing context",
            "plan-gate": "Planning",
            "reflect-trigger": "Reconsidering",
            "verify-gate": "Verifying",
        };
        return labels[event] || "Working";
    }

    _userLine(text) {
        const bubble = this._bubble("user");
        bubble.textContent = text;
        this._append(bubble.parentElement);
    }

    _agent(text) {
        const bubble = this._bubble("agent");
        bubble.innerHTML = this._renderMarkdown(text);
        this._append(bubble.parentElement);
    }

    _errorLine(text) {
        const bubble = this._bubble("error");
        bubble.textContent = text;
        this._append(bubble.parentElement);
    }

    _bubble(kind) {
        const row = document.createElement("div");
        row.className = `docs-chat-msg ${kind}`;
        const bubble = document.createElement("div");
        bubble.className = "docs-chat-bubble";
        row.appendChild(bubble);
        return bubble;
    }

    _renderMarkdown(text) {
        const html = this.markdown.render(text);
        if (window.DOMPurify && typeof window.DOMPurify.sanitize === "function") {
            return window.DOMPurify.sanitize(html);
        }
        return html;
    }

    _renderHistory(messages) {
        this.log.innerHTML = "";
        (messages || []).forEach((message) => {
            if (message.role === "user") {
                this._userLine(message.content);
                this._lastHistoryQuestion = (message.content || "").trim();
            } else if (message.role === "assistant" && message.content) {
                this._agent(message.content);
            }
        });
    }

    _onLogClick(event) {
        const button = event.target.closest(".md-copy");
        if (!button) return;
        const wrap = button.closest(".md-codewrap");
        const code = wrap && wrap.querySelector("code");
        if (!code) return;
        const label = button.textContent;
        const restore = () => {
            button.textContent = "Copied";
            window.setTimeout(() => {
                button.textContent = label;
            }, 1200);
        };
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(code.textContent).then(restore, () => {});
        }
    }

    _append(node) {
        const pinned = this._atBottom();
        this.log.appendChild(node);
        if (pinned) this.log.scrollTop = this.log.scrollHeight;
    }

    _atBottom() {
        return this.log.scrollHeight - this.log.scrollTop - this.log.clientHeight < 80;
    }

    _autosize() {
        this.field.style.height = "auto";
        this.field.style.height = `${Math.min(this.field.scrollHeight, 160)}px`;
    }

    _setStatus(text) {
        this.status.textContent = text || "";
        this.status.hidden = !text;
    }
}

customElements.define("dp-docs-chat", AppDocsChat);
