// retoor <retoor@molodetz.nl>

import { Component } from "./Component.js";
import { assetUrl } from "../assetVersion.js";

const FW_CSS_ID = "floating-window-css";
const MOBILE_QUERY = "(max-width: 640px)";
const KEYBOARD_INSET_THRESHOLD = 120;

export default class FloatingWindow extends Component {
  constructor() {
    super();
    this.state = "normal";
    this.geometry = null;
    this._drag = null;
    this._rafResize = null;
    this._mobile = window.matchMedia(MOBILE_QUERY);
    this._vv = window.visualViewport || null;
  }

  get _safeViewport() {
    return {
      width: window.innerWidth,
      height: this._vv ? this._vv.height : window.innerHeight,
    };
  }

  get _keyboardVisible() {
    return !!(this._vv && window.innerHeight - this._vv.height > KEYBOARD_INSET_THRESHOLD);
  }

  get windowKey() {
    return "floating-window";
  }

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

  get fontControls() {
    return false;
  }

  get _minimizeSize() {
    return [400, 260];
  }

  get _normalizeSize() {
    return [820, 560];
  }

  get _dragClass() {
    return "fw-dragging";
  }

  connectedCallback() {
    this._ensureBaseCss();
    this.classList.add("fw-host");
    this._buildChrome();
    this._registerWindow();
    this._viewportHandler = () => this._onViewportChange();
    this._mobile.addEventListener("change", this._viewportHandler);
    this._winResize = () => {
      this._clampWindow();
      this._scheduleResize();
    };
    window.addEventListener("resize", this._winResize);
    this._vvHandler = () => this._onVisualViewport();
    if (this._vv) {
      this._vv.addEventListener("resize", this._vvHandler);
      this._vv.addEventListener("scroll", this._vvHandler);
    }
    const saved = this._loadGeometry();
    if (saved) {
      this.geometry = saved;
      this._clampGeometry();
    }
    this._setState(this._mobile.matches ? "fullscreen" : "normal");
    this._buildBody(this.body);
    this._observeResize();
    this._onShow();
  }

  disconnectedCallback() {
    window.removeEventListener("resize", this._winResize);
    this._mobile.removeEventListener("change", this._viewportHandler);
    if (this._vv && this._vvHandler) {
      this._vv.removeEventListener("resize", this._vvHandler);
      this._vv.removeEventListener("scroll", this._vvHandler);
    }
    if (this._ro) this._ro.disconnect();
    if (globalThis.app && globalThis.app.windows) globalThis.app.windows.unregister(this);
    this._onHide();
  }

  _ensureBaseCss() {
    if (document.getElementById(FW_CSS_ID)) return;
    const link = document.createElement("link");
    link.id = FW_CSS_ID;
    link.rel = "stylesheet";
    link.href = assetUrl("/static/css/floating-window.css");
    document.head.appendChild(link);
  }

  _registerWindow() {
    if (globalThis.app && globalThis.app.windows) {
      globalThis.app.windows.register(this);
    }
    this.addEventListener("pointerdown", () => {
      if (globalThis.app && globalThis.app.windows) globalThis.app.windows.focus(this);
    });
    if (globalThis.app && globalThis.app.contextMenu) {
      globalThis.app.contextMenu.attach(this.win, () => this._contextItems());
    }
  }

  _buildChrome() {
    this.innerHTML = "";
    this.win = document.createElement("div");
    this.win.className = "fw-window";
    this.win.setAttribute("role", "dialog");
    const titleId = `fw-title-${Math.random().toString(36).slice(2, 9)}`;
    const fontButtons = this.fontControls ? [
      '    <button type="button" data-win="font-dec" title="Smaller font">&#8722;</button>',
      '    <button type="button" data-win="font-inc" title="Larger font">&#43;</button>',
    ] : [];
    this.win.innerHTML = [
      '<div class="fw-titlebar">',
      `  <span class="fw-title"><span class="fw-dot" aria-hidden="true"></span><span class="fw-title-text" id="${titleId}"></span></span>`,
      '  <span class="fw-winctl">',
      ...fontButtons,
      '    <button type="button" data-win="minimize" title="Minimize (smallest usable size)">&#9643;</button>',
      '    <button type="button" data-win="normalize" title="Normalize (comfortable size)">&#9645;</button>',
      '    <button type="button" data-win="fullscreen" title="Fullscreen">&#9974;</button>',
      '    <button type="button" data-win="maximize" title="Maximize">&#9633;</button>',
      '    <button type="button" data-win="close" title="Close">&#10005;</button>',
      "  </span>",
      "</div>",
      '<div class="fw-body"></div>',
      '<div class="fw-resize"></div>',
    ].join("");
    this.appendChild(this.win);
    this.win.setAttribute("aria-labelledby", titleId);
    this.titlebar = this.win.querySelector(".fw-titlebar");
    this.body = this.win.querySelector(".fw-body");
    this.win.querySelector(".fw-title-text").textContent = this.titleText;
    this.win.querySelectorAll(".fw-winctl button").forEach((button) => {
      if (button.title) button.setAttribute("aria-label", button.title);
      button.addEventListener("click", () => this._window(button.dataset.win));
    });
    this.titlebar.addEventListener("mousedown", (event) => this._startDrag(event));
    this.titlebar.addEventListener("touchstart", (event) => this._startDrag(event), { passive: false });
    this.titlebar.addEventListener("dblclick", (event) => {
      if (event.target.closest("button")) return;
      this._window("maximize");
    });
    const grip = this.win.querySelector(".fw-resize");
    grip.addEventListener("mousedown", (event) => this._startResize(event));
    grip.addEventListener("touchstart", (event) => this._startResize(event), { passive: false });
  }

  setTitle(text) {
    const el = this.win && this.win.querySelector(".fw-title-text");
    if (el) el.textContent = text;
  }

  _buildBody() {}
  _onShow() {}
  _onHide() {}
  _onResize() {}
  _onClose() { this.remove(); }
  _changeFont() {}
  _fontAtMin() { return false; }
  _fontAtMax() { return false; }

  _showCtl(name, visible) {
    const btn = this.win && this.win.querySelector(`button[data-win="${name}"]`);
    if (btn) btn.hidden = !visible;
  }

  _atSize([width, height]) {
    if (!this.geometry) return false;
    const w = Math.max(320, Math.min(width, window.innerWidth - 16));
    const h = Math.max(220, Math.min(height, this._safeViewport.height - 16));
    return Math.abs(this.geometry.width - w) <= 4 && Math.abs(this.geometry.height - h) <= 4;
  }

  _syncControls() {
    if (!this.win) return;
    const normal = this.state === "normal";
    this._showCtl("maximize", this.state !== "maximized");
    this._showCtl("fullscreen", this.state !== "fullscreen");
    this._showCtl("minimize", !(normal && this._atSize(this._minimizeSize)));
    this._showCtl("normalize", !(normal && this._atSize(this._normalizeSize)));
    if (this.fontControls) {
      this._showCtl("font-dec", !this._fontAtMin());
      this._showCtl("font-inc", !this._fontAtMax());
    }
  }

  close() {
    this._onClose();
  }

  _window(action) {
    if (action === "close") {
      this.close();
    } else if (action === "maximize") {
      this._setState(this.state === "maximized" ? "normal" : "maximized");
    } else if (action === "fullscreen") {
      this._setState(this.state === "fullscreen" ? "normal" : "fullscreen");
    } else if (action === "font-inc") {
      this._changeFont(1);
    } else if (action === "font-dec") {
      this._changeFont(-1);
    } else if (action === "minimize") {
      this.minimizeWindow();
    } else if (action === "normalize") {
      this.normalizeWindow();
    }
    if (action !== "close") this._syncControls();
  }

  minimizeWindow() {
    this._presetGeometry(...this._minimizeSize);
  }

  normalizeWindow() {
    this._presetGeometry(...this._normalizeSize);
  }

  _presetGeometry(width, height) {
    const safeHeight = this._safeViewport.height;
    const w = Math.max(320, Math.min(width, window.innerWidth - 16));
    const h = Math.max(220, Math.min(height, safeHeight - 16));
    this.geometry = {
      width: w,
      height: h,
      left: Math.max(8, Math.round((window.innerWidth - w) / 2)),
      top: Math.max(8, Math.round((safeHeight - h) / 2)),
    };
    this._setState("normal");
    this._persistGeometry();
  }

  _contextItems() {
    return [
      { label: "Minimize", onSelect: () => this.minimizeWindow() },
      { label: "Normalize", onSelect: () => this.normalizeWindow() },
      { separator: true },
      { label: "Close", danger: true, onSelect: () => this.close() },
    ];
  }

  _setState(state) {
    let resolved = state;
    if (this._mobile.matches) resolved = "fullscreen";
    this.state = resolved;
    this.classList.toggle("fw-mobile", this._mobile.matches);
    ["normal", "maximized", "fullscreen"].forEach((name) => {
      this.classList.toggle(`fw-state-${name}`, name === resolved);
    });
    if (resolved === "normal") {
      this._applyGeometry();
    } else {
      this.win.style.left = "";
      this.win.style.top = "";
      this.win.style.width = "";
      this.win.style.height = "";
    }
    this._syncControls();
    this._scheduleResize();
  }

  _defaultGeometry() {
    const safeHeight = this._safeViewport.height;
    const width = Math.min(820, Math.round(window.innerWidth * 0.94));
    const height = Math.min(560, Math.round(safeHeight * 0.82));
    return {
      width,
      height,
      left: Math.max(12, Math.round((window.innerWidth - width) / 2)),
      top: Math.max(12, Math.round((safeHeight - height) / 2)),
    };
  }

  _applyGeometry() {
    if (!this.geometry) this.geometry = this._defaultGeometry();
    const g = this.geometry;
    this.win.style.width = `${g.width}px`;
    this.win.style.height = `${g.height}px`;
    this.win.style.left = `${g.left}px`;
    this.win.style.top = `${g.top}px`;
  }

  _clampGeometry() {
    if (!this.geometry) return;
    const g = this.geometry;
    const safeHeight = this._safeViewport.height;
    g.width = Math.min(g.width, window.innerWidth - 16);
    g.height = Math.min(g.height, safeHeight - 16);
    g.left = Math.max(8, Math.min(g.left, window.innerWidth - g.width - 8));
    g.top = Math.max(8, Math.min(g.top, safeHeight - g.height - 8));
  }

  _clampWindow() {
    if (this.state !== "normal" || !this.geometry) return;
    this._clampGeometry();
    this._applyGeometry();
    this._persistGeometry();
    this._syncControls();
    this._scheduleResize();
  }

  _onViewportChange() {
    this._setState("normal");
  }

  _onVisualViewport() {
    if (!this._vv) return;
    this.classList.toggle("fw-keyboard-visible", this._keyboardVisible);
    if (this.win && (this.state === "fullscreen" || this.state === "maximized")) {
      this.win.style.height = `${this._vv.height}px`;
      this.win.style.top = `${this._vv.offsetTop}px`;
    }
    this._scheduleResize();
  }

  _startDrag(event) {
    if (this.state !== "normal") return;
    if (event.target.tagName === "BUTTON") return;
    event.preventDefault();
    const point = event.touches ? event.touches[0] : event;
    const rect = this.win.getBoundingClientRect();
    this._drag = { dx: point.clientX - rect.left, dy: point.clientY - rect.top };
    this.classList.add(this._dragClass);
    this._moveHandler = (move) => this._onDrag(move);
    this._upHandler = () => this._endDrag();
    window.addEventListener("mousemove", this._moveHandler);
    window.addEventListener("mouseup", this._upHandler);
    window.addEventListener("touchmove", this._moveHandler, { passive: false });
    window.addEventListener("touchend", this._upHandler);
  }

  _onDrag(event) {
    if (!this._drag) return;
    if (event.cancelable) event.preventDefault();
    const point = event.touches ? event.touches[0] : event;
    const g = this.geometry;
    const safeHeight = this._safeViewport.height;
    g.left = Math.max(8, Math.min(point.clientX - this._drag.dx, window.innerWidth - g.width - 8));
    g.top = Math.max(8, Math.min(point.clientY - this._drag.dy, safeHeight - g.height - 8));
    this.win.style.left = `${g.left}px`;
    this.win.style.top = `${g.top}px`;
  }

  _endDrag() {
    this._drag = null;
    this.classList.remove(this._dragClass);
    window.removeEventListener("mousemove", this._moveHandler);
    window.removeEventListener("mouseup", this._upHandler);
    window.removeEventListener("touchmove", this._moveHandler);
    window.removeEventListener("touchend", this._upHandler);
    this._persistGeometry();
  }

  _startResize(event) {
    if (this.state !== "normal") return;
    event.preventDefault();
    event.stopPropagation();
    if (!this.geometry) this.geometry = this._defaultGeometry();
    const point = event.touches ? event.touches[0] : event;
    this._resize = { x: point.clientX, y: point.clientY, w: this.geometry.width, h: this.geometry.height };
    this.classList.add(this._dragClass);
    this._resizeMove = (move) => this._onResizeDrag(move);
    this._resizeUp = () => this._endResize();
    window.addEventListener("mousemove", this._resizeMove);
    window.addEventListener("mouseup", this._resizeUp);
    window.addEventListener("touchmove", this._resizeMove, { passive: false });
    window.addEventListener("touchend", this._resizeUp);
  }

  _onResizeDrag(event) {
    if (!this._resize) return;
    if (event.cancelable) event.preventDefault();
    const point = event.touches ? event.touches[0] : event;
    const g = this.geometry;
    const safeHeight = this._safeViewport.height;
    g.width = Math.max(320, Math.min(this._resize.w + (point.clientX - this._resize.x), window.innerWidth - g.left - 8));
    g.height = Math.max(220, Math.min(this._resize.h + (point.clientY - this._resize.y), safeHeight - g.top - 8));
    this.win.style.width = `${g.width}px`;
    this.win.style.height = `${g.height}px`;
    this._scheduleResize();
  }

  _endResize() {
    this._resize = null;
    this.classList.remove(this._dragClass);
    window.removeEventListener("mousemove", this._resizeMove);
    window.removeEventListener("mouseup", this._resizeUp);
    window.removeEventListener("touchmove", this._resizeMove);
    window.removeEventListener("touchend", this._resizeUp);
    this._persistGeometry();
    this._syncControls();
  }

  _observeResize() {
    if (typeof ResizeObserver === "undefined") return;
    this._ro = new ResizeObserver(() => {
      if (this.state === "normal" && this.geometry && !this._drag) {
        const rect = this.win.getBoundingClientRect();
        this.geometry.width = Math.round(rect.width);
        this.geometry.height = Math.round(rect.height);
        this._persistGeometry();
      }
      this._syncControls();
      this._scheduleResize();
    });
    this._ro.observe(this.win);
    if (this.body) this._ro.observe(this.body);
  }

  _scheduleResize() {
    if (this._rafResize) return;
    this._rafResize = window.requestAnimationFrame(() => {
      this._rafResize = null;
      this._onResize();
    });
  }

  _loadGeometry() {
    try {
      const raw = window.localStorage.getItem(`fw-geo:${this.windowKey}`);
      return raw ? JSON.parse(raw) : null;
    } catch (error) {
      return null;
    }
  }

  _persistGeometry() {
    try {
      window.localStorage.setItem(`fw-geo:${this.windowKey}`, JSON.stringify(this.geometry));
    } catch (error) {
      return;
    }
  }
}
