import { extensions, ExtensionType } from '@pixi/extensions';
import type { ExtensionMetadata } from '@pixi/extensions';
import type { Matrix } from '@pixi/math';
import type { IRenderableObject, IRendererRenderOptions } from '../IRenderer';
import type { Renderer } from '../Renderer';
import type { RenderTexture } from '../renderTexture/RenderTexture';
import type { ISystem } from '../system/ISystem';
/**
 * system that provides a render function that focussing on rendering Pixi Scene Graph objects
 * to either the main view or to a renderTexture.  Used for Canvas `WebGL` contexts
 * @memberof PIXI
 */
export class ObjectRendererSystem implements ISystem
{
    /** @ignore */
    static extension: ExtensionMetadata = {
        type: ExtensionType.RendererSystem,
        name: 'objectRenderer',
    };
    renderer: Renderer;
    /**
     * Flag if we are rendering to the screen vs renderTexture
     * @readonly
     * @default true
     */
    renderingToScreen: boolean;
    /**
     * the last object rendered by the renderer. Useful for other plugins like interaction managers
     * @readonly
     */
    lastObjectRendered: IRenderableObject;
    // renderers scene graph!
    constructor(renderer: Renderer)
    {
        this.renderer = renderer;
    }
    /**
     * Renders the object to its WebGL view.
     * @param displayObject - The object to be rendered.
     * @param options - the options to be passed to the renderer
     */
    render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void
    {
        const renderer = this.renderer;
        let renderTexture: RenderTexture;
        let clear: boolean;
        let transform: Matrix;
        let skipUpdateTransform: boolean;
        if (options)
        {
            renderTexture = options.renderTexture;
            clear = options.clear;
            transform = options.transform;
            skipUpdateTransform = options.skipUpdateTransform;
        }
        // can be handy to know!
        this.renderingToScreen = !renderTexture;
        renderer.runners.prerender.emit();
        renderer.emit('prerender');
        // apply a transform at a GPU level
        renderer.projection.transform = transform;
        // no point rendering if our context has been blown up!
        if (renderer.context.isLost)
        {
            return;
        }
        if (!renderTexture)
        {
            this.lastObjectRendered = displayObject;
        }
        if (!skipUpdateTransform)
        {
            // update the scene graph
            const cacheParent = displayObject.enableTempParent();
            displayObject.updateTransform();
            displayObject.disableTempParent(cacheParent);
            // displayObject.hitArea = //TODO add a temp hit area
        }
        renderer.renderTexture.bind(renderTexture);
        renderer.batch.currentRenderer.start();
        if (clear ?? renderer.background.clearBeforeRender)
        {
            renderer.renderTexture.clear();
        }
        displayObject.render(renderer);
        // apply transform..
        renderer.batch.currentRenderer.flush();
        if (renderTexture)
        {
            if (options.blit)
            {
                renderer.framebuffer.blit();
            }
            renderTexture.baseTexture.update();
        }
        renderer.runners.postrender.emit();
        // reset transform after render
        renderer.projection.transform = null;
        renderer.emit('postrender');
    }
    destroy(): void
    {
        // ka pow!
        this.renderer = null;
        this.lastObjectRendered = null;
    }
}
extensions.add(ObjectRendererSystem);