// retoor <retoor@molodetz.nl>

import FloatingWindow from "./FloatingWindow.js";
import { Http } from "../Http.js";
import { UserScope } from "../userScope.js";
import { assetUrl } from "../assetVersion.js";

const XTERM_JS = assetUrl("/static/vendor/xterm/xterm.js");
const XTERM_CSS = assetUrl("/static/vendor/xterm/xterm.css");
const XTERM_FIT = assetUrl("/static/vendor/xterm/addon-fit.js");

const FONT_MIN = 8;
const FONT_MAX = 30;
const FONT_DEFAULT = 13;

let xtermLoading = null;
let cascade = 0;

function fontKey() {
  return `ct-fontsize:${UserScope.get()}`;
}

function readFontSize() {
  try {
    const value = parseInt(window.localStorage.getItem(fontKey()), 10);
    return Number.isFinite(value) ? Math.max(FONT_MIN, Math.min(FONT_MAX, value)) : FONT_DEFAULT;
  } catch (error) {
    return FONT_DEFAULT;
  }
}

function writeFontSize(size) {
  try {
    window.localStorage.setItem(fontKey(), String(size));
  } catch (error) {
    return;
  }
}

function loadScript(src) {
  return new Promise((resolve, reject) => {
    if (document.querySelector(`script[src="${src}"]`)) {
      resolve();
      return;
    }
    const script = document.createElement("script");
    script.src = src;
    script.onload = () => resolve();
    script.onerror = () => reject(new Error(`failed to load ${src}`));
    document.head.appendChild(script);
  });
}

function loadCss(href, id) {
  if (document.getElementById(id)) return;
  const link = document.createElement("link");
  link.id = id;
  link.rel = "stylesheet";
  link.href = href;
  document.head.appendChild(link);
}

function ensureXterm() {
  if (window.Terminal && window.FitAddon) return Promise.resolve();
  if (!xtermLoading) {
    loadCss(XTERM_CSS, "xterm-css");
    xtermLoading = loadScript(XTERM_JS).then(() => loadScript(XTERM_FIT));
  }
  return xtermLoading;
}

export default class ContainerTerminalElement extends FloatingWindow {
  get windowKey() {
    return "container-terminal";
  }

  get titleText() {
    return this.getAttribute("label") || this.getAttribute("instance") || "terminal";
  }

  get fontControls() {
    return true;
  }

  get isPersistent() {
    return this.hasAttribute("persist");
  }

  setPersistent(on) {
    if (on) {
      this.setAttribute("persist", "");
    } else {
      this.removeAttribute("persist");
      this._clearReconnect();
    }
  }

  _clearReconnect() {
    if (this._reconnectTimer) {
      window.clearTimeout(this._reconnectTimer);
      this._reconnectTimer = null;
    }
  }

  _fontAtMin() {
    return readFontSize() <= FONT_MIN;
  }

  _fontAtMax() {
    return readFontSize() >= FONT_MAX;
  }

  setFontSize(size) {
    if (!this.term) return;
    this.term.options.fontSize = size;
    this._onResize();
  }

  _changeFont(delta) {
    const next = Math.max(FONT_MIN, Math.min(FONT_MAX, readFontSize() + delta));
    writeFontSize(next);
    if (globalThis.app && globalThis.app.containerTerminals) {
      globalThis.app.containerTerminals.applyFontSize(next);
    } else {
      this.setFontSize(next);
    }
  }

  _defaultGeometry() {
    const geometry = super._defaultGeometry();
    const offset = (cascade++ % 6) * 26;
    geometry.left = Math.max(12, geometry.left + offset);
    geometry.top = Math.max(12, geometry.top + offset);
    return geometry;
  }

  async _buildBody(body) {
    body.classList.add("ct-body");
    this._status = document.createElement("div");
    this._status.className = "ct-status";
    this._status.textContent = "connecting...";
    body.appendChild(this._status);
    this._mount = document.createElement("div");
    this._mount.className = "ct-xterm";
    body.appendChild(this._mount);
    try {
      await ensureXterm();
    } catch (error) {
      this._status.textContent = "failed to load terminal assets";
      return;
    }
    if (!this.isConnected) return;
    this._initTerm();
  }

  _initTerm() {
    const Terminal = window.Terminal;
    const FitAddon = window.FitAddon.FitAddon;
    this.term = new Terminal({
      cursorBlink: true,
      fontFamily: '"SFMono-Regular", "JetBrains Mono", "Fira Code", Consolas, "Courier New", monospace',
      fontSize: readFontSize(),
      theme: { background: "#000000", foreground: "#d0d0d0" },
      scrollback: 5000,
    });
    this.fit = new FitAddon();
    this.term.loadAddon(this.fit);
    this.term.open(this._mount);
    this.term.onData((data) => {
      if (this._ws && this._ws.readyState === 1) this._ws.send(data);
    });
    this._fitSoon();
    this._connect();
  }

  _fitSoon() {
    [0, 60, 180, 400].forEach((delay) => window.setTimeout(() => this._onResize(), delay));
  }

  _connect() {
    const slug = this.getAttribute("project-slug");
    const instance = this.getAttribute("instance");
    const session = this.getAttribute("session") || "";
    const scheme = location.protocol === "https:" ? "wss" : "ws";
    const query = session ? `?session=${encodeURIComponent(session)}` : "";
    const url = `${scheme}://${location.host}/projects/${encodeURIComponent(slug)}`
      + `/containers/instances/${encodeURIComponent(instance)}/exec/ws${query}`;
    const ws = new WebSocket(url);
    this._ws = ws;
    ws.addEventListener("open", () => {
      this._reconnects = 0;
      if (this._status) this._status.style.display = "none";
      this._fitSoon();
      if (this.term) this.term.focus();
    });
    ws.addEventListener("message", (event) => {
      if (this.term) this.term.write(event.data);
    });
    ws.addEventListener("close", (event) => {
      this._ws = null;
      if (this._closing) return;
      if (event.code === 1008) {
        if (this.term) this.term.write("\r\n\x1b[31m[forbidden]\x1b[0m\r\n");
        return;
      }
      if (this.isPersistent) {
        this._scheduleReconnect();
      } else if (this.term) {
        this.term.write("\r\n\x1b[31m[disconnected]\x1b[0m\r\n");
      }
    });
    ws.addEventListener("error", () => {});
  }

  _scheduleReconnect() {
    if (this._closing || this._reconnectTimer) return;
    this._reconnects = (this._reconnects || 0) + 1;
    const delay = Math.min(5000, 400 * this._reconnects);
    if (this._status) {
      this._status.style.display = "";
      this._status.textContent = "reconnecting...";
    }
    this._reconnectTimer = window.setTimeout(() => {
      this._reconnectTimer = null;
      if (!this._closing && this.isConnected) this._connect();
    }, delay);
  }

  _sendResize() {
    if (!this.term || !this._ws || this._ws.readyState !== 1) return;
    this._ws.send(JSON.stringify({ type: "resize", cols: this.term.cols, rows: this.term.rows }));
  }

  _onResize() {
    if (!this.fit || !this.term || !this.isConnected) return;
    try {
      this.fit.fit();
    } catch (error) {
      return;
    }
    this._sendResize();
    if (this.term.scrollToBottom) this.term.scrollToBottom();
  }

  _onShow() {
    if (this.term) this.term.focus();
  }

  _contextItems() {
    const slug = this.getAttribute("project-slug");
    return [
      { label: "Copy", disabled: !(this.term && this.term.hasSelection()), onSelect: () => this._copySelection() },
      { label: "Paste", onSelect: () => this._paste() },
      { separator: true },
      { label: "Sync files", onSelect: () => this._lifecycle("sync", "Files synced") },
      { label: "Restart", onSelect: () => this._lifecycle("restart", "Restart requested") },
      { label: "Terminate", danger: true, onSelect: () => this._terminate() },
      { separator: true },
      { label: "Minimize", onSelect: () => this.minimizeWindow() },
      { label: "Normalize", onSelect: () => this.normalizeWindow() },
      { separator: true },
      { label: "Project", onSelect: () => this._navigate(`/projects/${slug}`) },
      { label: "Files", onSelect: () => this._navigate(`/projects/${slug}/files`) },
      { separator: true },
      { label: "Close", danger: true, onSelect: () => this.close() },
    ];
  }

  async _lifecycle(action, okMessage) {
    const slug = this.getAttribute("project-slug");
    const instance = this.getAttribute("instance");
    const url = `/projects/${encodeURIComponent(slug)}/containers/instances/${encodeURIComponent(instance)}/${action}`;
    try {
      await Http.send(url, {});
      this._toast(okMessage, "success");
    } catch (error) {
      this._toast(error.message || `${action} failed`, "error");
    }
  }

  async _terminate() {
    if (globalThis.app && globalThis.app.dialog) {
      const ok = await globalThis.app.dialog.confirm({
        title: "Terminate container",
        message: `Stop and remove "${this.titleText}"? Synced project files are kept.`,
        confirmLabel: "Terminate",
        danger: true,
      });
      if (!ok) return;
    }
    await this._lifecycle("delete", "Container terminated");
    this.close();
  }

  _copySelection() {
    if (!this.term || !this.term.hasSelection()) return;
    this._clipboardWrite(this.term.getSelection());
  }

  async _paste() {
    if (!navigator.clipboard || !navigator.clipboard.readText) {
      this._toast("clipboard read unavailable", "error");
      return;
    }
    let text = "";
    try {
      text = await navigator.clipboard.readText();
    } catch (error) {
      this._toast("paste failed", "error");
      return;
    }
    if (text && this._ws && this._ws.readyState === 1) this._ws.send(text);
    if (this.term) this.term.focus();
  }

  _clipboardWrite(text) {
    if (!text) return;
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).catch(() => this._clipboardWriteFallback(text));
    } else {
      this._clipboardWriteFallback(text);
    }
  }

  _clipboardWriteFallback(text) {
    const area = document.createElement("textarea");
    area.value = text;
    area.style.position = "fixed";
    area.style.opacity = "0";
    document.body.appendChild(area);
    area.select();
    try {
      document.execCommand("copy");
    } catch (error) {
      document.body.removeChild(area);
      return;
    }
    document.body.removeChild(area);
  }

  _navigate(url) {
    window.location.assign(url);
  }

  _toast(message, type) {
    if (globalThis.app && globalThis.app.toast) globalThis.app.toast.show(message, { type: type || "info" });
  }

  _onClose() {
    this._closing = true;
    this._clearReconnect();
    if (globalThis.app && globalThis.app.containerTerminals) {
      globalThis.app.containerTerminals.forget(this);
    }
    try {
      if (this._ws) this._ws.close();
    } catch (error) {
      return this._dispose();
    }
    this._dispose();
  }

  _dispose() {
    try {
      if (this.term) this.term.dispose();
    } catch (error) {
    }
    this.remove();
  }
}

customElements.define("container-terminal", ContainerTerminalElement);
