import {Container, DisplayObjectEvents, Renderer} from 'pixi.js';
import { ResizeEventDetail, Service, ViewportOptions } from '../types';

/**
 * Viewport is a resizable PIXI.Container that scales your scene to fit the full window height
 * and crops any excess width. It maintains a fixed virtual resolution and adjusts the scale dynamically.
 *
 * The `Viewport` is designed to always match the **window's height** and center the content horizontally,
 * potentially cropping left/right edges depending on the screen's aspect ratio.
 *
 * Used via the `Locator` system as a service with `serviceType = "viewport"`.
 *
 * Example:
 * ```ts
 * Locator.provide(new Viewport({ width: 1920, height: 1080 }, renderer));
 * const viewport = Locator.viewport;
 * viewport.addChild(scene);
 * ```
 *
 * ## 📐 Crop Layout
 * The height of the virtual resolution is always fully shown.
 * The width is scaled proportionally and may be cropped to stay centered.
 *
 * Visual breakdown:
 *
 * ```
 * ┌────────────────────────────── window ──────────────────────────────┐
 * │                                                                    │
 * │  [← cropped →][←─          visible scene         ─→][← cropped →]  │
 * │                                                                    │
 * └────────────────────────────────────────────────────────────────────┘
 * ```
 *
 * - The **visible** area is centered and scaled to match the window height.
 * - **Left** and **right** areas may be hidden (cropped) if the screen is too wide.
 *
 * ## Ratios used:
 * - `heightRatio = window.innerHeight / virtualHeight`
 * - `widthRatio = window.innerWidth / virtualWidth`
 * - `cropWidthRatio = clamp(widthRatio / heightRatio, 0.2, 1)`
 * - `scale = heightRatio`
 *
 * A `"resize"` event is emitted when the viewport is resized, containing layout data.
 */

export type ViewportEvents = "resize" | DisplayObjectEvents;

export default class Viewport extends Container implements Service
{
    /** Unique identifier used by the Locator to register the service. */
    public readonly serviceType: string = 'viewport';
    public override name = 'Viewport';

    private renderer: Renderer;
    private options: ViewportOptions;

    /**
     * Constructs a new Viewport service.
     * @param options - The virtual resolution dimensions.
     * @param renderer - The PIXI renderer instance used to resize the screen.
     */
    constructor(options: ViewportOptions, renderer: Renderer)
    {
        super();
        this.renderer = renderer;
        this.options = options;

        this.sortableChildren = true;

        this.resize = this.resize.bind(this);
        window.addEventListener('resize', this.resize);
    }

    /**
     * Recalculates scale and position based on the current window size,
     * resizes the renderer, and emits a "resize" event with layout details.
     *
     * The emitted event contains:
     * - viewport size,
     * - visible (cropped) size,
     * - center position,
     * - left and right visible bounds,
     * - current width/height ratios.
     */
    resize(): void
    {
        const sh = window.innerHeight;
        const sw = Math.min(window.innerWidth, this.options.width * this.heightRatio);

        this.renderer.resize(sw, sh);
        this.scale.set(this.heightRatio);

        if (this.options.width * this.heightRatio > window.innerWidth)
        {
            this.x = -((this.options.width * this.heightRatio) - window.innerWidth) * 0.5;
        }
        else
        {
            this.x = 0;
        }

        const detail: ResizeEventDetail = {
            viewportWidth: this.options.width,
            viewportHeight: this.options.height,
            visibleWidth: this.options.width * this.cropWidthRatio,
            visibleHeight: this.options.height,
            center: { x: this.options.width * 0.5, y: this.options.height * 0.5 },
            left: (this.options.width * 0.5) - (this.options.width * this.cropWidthRatio * 0.5),
            right: (this.options.width * 0.5) + (this.options.width * this.cropWidthRatio * 0.5),
            cropWidthRatio: this.cropWidthRatio,
            heightRatio: this.heightRatio,
        };

        //@ts-expect-error event type
        this.emit('resize', detail);
    }

    /** Ratio of visible width based on current aspect ratio, clamped between 0.2 and 1. */
    get cropWidthRatio(): number
    {
        const ratio = Math.min(1, this.widthRatio / this.heightRatio);

        return Math.max(0.2, ratio);
    }

    /** Ratio between actual screen width and virtual width. */
    get widthRatio(): number
    {
        return window.innerWidth / this.options.width;
    }

    /** Ratio between actual screen height and virtual height. */
    get heightRatio(): number
    {
        return window.innerHeight / this.options.height;
    }

    /** The left edge of the visible area in virtual coordinates. */
    get left(): number
    {
        return this.options.width * (1 - this.cropWidthRatio) * 0.5;
    }

    /** The right edge of the visible area in virtual coordinates. */
    get right(): number
    {
        return this.options.width - this.left;
    }

    /** Returns true if the device is currently in landscape orientation. */
    isLandscape(): boolean
    {
        return typeof window !== 'undefined' && matchMedia('(orientation: landscape)').matches;
    }

    /** Cleans up the service by removing window event listeners and destroying the PIXI container. */
    dispose(): void
    {
        window.removeEventListener('resize', this.resize);
        this.destroy({ children: true, texture: false, baseTexture: false });
        this.renderer = undefined;
    }
}
