Popup.tstypescript
import { CLOSE_ICON } from "./ui/icons";
import { createLonLat, stringTemplate } from "./utils/shared";
import type { EventsHandler } from "./Events";
import { LonLat } from "./LonLat";
import { Planet } from "./scene/Planet";
import { Vec2 } from "./math/Vec2";
import { Vec3 } from "./math/Vec3";
import type { NumberArray2 } from "./math/Vec2";
import type { NumberArray3 } from "./math/Vec3";
import { View } from "./ui/View";
import type { IViewParams, ViewEventsList } from "./ui/View";

const TEMPLATE = `<div class="og-popup {className}">
      <div class="og-popup-content-wrapper">
        <div class="og-popup-content"></div>
      </div>
      <div class="og-popup-tip-container">
        <div class="og-popup-tip"></div>
      </div>
      <div class="og-popup-toolbar">
        <div class="og-popup-btn og-popup-close">${CLOSE_ICON}</div>
      </div>
      <div class="og-popup-title">{title}</div>
    </div>`;

interface IPopupParams extends IViewParams {
    planet: Planet;
    title?: string;
    className?: string;
    visibility?: boolean;
    content?: string;
    offset?: NumberArray2;
    lonLat?: LonLat | NumberArray2 | NumberArray3;
}

type PopupEventsList = ["open", "close"];
const POPUP_EVENTS: PopupEventsList = ["open", "close"];

class Popup extends View<null> {
    public override events: EventsHandler<PopupEventsList> & EventsHandler<ViewEventsList>;

    public $content: HTMLElement | null;
    public $tip: HTMLElement | null;
    public $title: HTMLElement | null;

    protected _content: string | HTMLElement | null;

    protected _planet: Planet;

    protected _offset: NumberArray2;

    protected _lonLat: LonLat;

    protected _cartPos: Vec3;

    protected _visibility: boolean;

    constructor(options: IPopupParams) {
        super({
            template: stringTemplate(TEMPLATE, {
                title: options.title || ""
            }),
            classList: options.className ? [options.className] : [],
            ...options
        });

        //@ts-ignore
        this.events = this.events.registerNames(POPUP_EVENTS);

        this._content = options.content || "";

        this.$content = null;
        this.$tip = null;
        this.$title = null;

        this._planet = options.planet;

        this._offset = options.offset || [0, 0];

        this._lonLat = createLonLat(options.lonLat);

        this._cartPos = new Vec3();

        this._visibility = options.visibility || false;

        this.render();
    }

    _updatePosition() {
        this.setCartesian3v(this._cartPos);
    }

    public setScreen(p: Vec2) {
        if (this._planet) {
            let r = this._planet.renderer!.handler.pixelRatio;
            this.el!.style.transform = `translate(${p.x / r - this.clientWidth * 0.5}px, ${p.y / r - this._planet.renderer!.handler.canvas!.clientHeight - this.$tip!.clientHeight}px)`;
        }
    }

    public get clientWidth(): number {
        return this.el ? this.el.clientWidth : 0;
    }

    public get clientHeight(): number {
        return this.el ? this.el.clientHeight : 0;
    }

    public setOffset(x: number = 0, y: number = 0): this {
        this._offset[0] = x;
        this._offset[1] = y;
        if (this.el) {
            this.el.style.left = `${x}px`;
            this.el.style.bottom = `${y}px`;
        }
        return this;
    }

    public override render(params?: any): this {
        super.render(params);

        this.$content = this.select(".og-popup-content")!;
        this.$title = this.select(".og-popup-title")!;
        this.$tip = this.select(".og-popup-tip-container")!;

        this.setOffset(this._offset[0], this._offset[1]);
        this.setContent(this._content);
        this.setLonLat(this._lonLat);
        this.setVisibility(this._visibility);
        this.select(".og-popup-close")!.addEventListener("click", () => {
            this.hide();
        });

        return this;
    }

    public setVisibility(visibility: boolean): this {
        if (visibility) {
            this.show();
        } else {
            this.hide();
        }
        return this;
    }

    public getContainer(): HTMLElement | null {
        return this.$content;
    }

    public getToolbarContainer(): HTMLElement {
        return this.select(".og-popup-toolbar")!;
    }

    public show(): this {
        this._visibility = true;
        if (this._planet) {
            this._planet.events.on("draw", this._updatePosition, this);
            this.appendTo(this._planet.renderer!.getInnerContainer());
            this.events.dispatch(this.events.open, this);
        }
        return this;
    }

    public hide(): this {
        this._visibility = false;
        if (this.el && this.el.parentNode) {
            this._planet.events.off("draw", this._updatePosition);
            this.el.parentNode.removeChild(this.el);
            this.events.dispatch(this.events.close, this);
        }
        return this;
    }

    public setCartesian3v(cart: Vec3, height: number = 0): this {
        this._cartPos = cart;

        if (this._planet) {
            let cam = this._planet.camera;
            let f = this._planet.ellipsoid.equatorialSize + height,
                g = cam._lonLat.height;

            let look = cart.sub(cam.eye),
                v = Math.sqrt((f + g) * (f + g) - f * f);

            if (v > look.length() && cam.getForward().dot(look.normalize()) > 0.0) {
                this.el!.style.display = "block";
                this.setScreen(cam.project3v(cart));
            } else {
                this.el!.style.display = "none";
            }
        }
        return this;
    }

    public setTitle(html: string) {
        if (this.$title) {
            this.$title.innerHTML = html;
        }
    }

    public setLonLat(lonLat: LonLat) {
        this._lonLat = lonLat;
        if (this._planet) {
            this.setCartesian3v(this._planet.ellipsoid.lonLatToCartesian(lonLat), lonLat.height);
        }
    }

    public setContent(content?: string | HTMLElement | null) {
        if (content) {
            this.clear();
            this._content = content;
            if (this.$content) {
                if (typeof content === "string") {
                    this.$content.innerHTML = content;
                } else {
                    this.$content.appendChild(content);
                }
            }
        }
    }

    public clear() {
        this._content = null;
        if (this.$content) {
            this.$content.innerHTML = "";
        }
    }
}

export { Popup };