/*
 * If not stated otherwise in this file or this component's LICENSE file the
 * following copyright and licenses apply:
 *
 * Copyright 2026 Comcast Cable Communications Management, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { describe, expect, it, vi } from 'vitest';
import { CoreTextNode, type CoreTextNodeProps } from '../../CoreTextNode.js';
import type { Stage } from '../../Stage.js';
import type { TextRenderer } from '../../text-rendering/TextRenderer.js';
import { WebGlRenderer } from './WebGlRenderer.js';
import { WebGlShaderProgram } from './WebGlShaderProgram.js';
import {
  makeMockStage,
  makeTextProps as makeSharedTextProps,
} from '../../../../test/mockStage.js';

const makeStage = (): Stage => makeMockStage({ pixelRatio: 2 });

const makeTextProps = (): CoreTextNodeProps =>
  makeSharedTextProps({ h: 20, w: 100 });

const makeSdfTextRenderer = (): TextRenderer =>
  ({
    clearCache: vi.fn(),
    type: 'sdf',
    font: {
      isFontLoaded: vi.fn().mockReturnValue(true),
      loadFont: vi.fn(),
      waitingForFont: vi.fn(),
      stopWaitingForFont: vi.fn(),
    },
    init: vi.fn(),
    renderText: vi.fn().mockReturnValue({
      width: 100,
      height: 20,
      layout: { glyphs: [], width: 100, height: 20 },
    }),
    addQuads: vi.fn().mockReturnValue(new Float32Array(0)),
    renderQuads: vi.fn(),
  } as unknown as TextRenderer);

const makeSdfTextNode = () => {
  const node = new CoreTextNode(
    makeStage(),
    makeTextProps(),
    makeSdfTextRenderer(),
  );
  node.parentHasRenderTexture = true;
  node.framebufferDimensions = { w: 320, h: 180 };
  node.rttParent = { framebufferDimensions: { w: 640, h: 360 } } as any;
  return node;
};

describe('WebGlShaderProgram.bindRenderOp', () => {
  function createProgram() {
    const program = Object.create(
      WebGlShaderProgram.prototype,
    ) as WebGlShaderProgram;
    const bindTextures = vi.fn();
    const bindBufferCollection = vi.fn();
    const uniform1f = vi.fn();
    const uniform2f = vi.fn();
    const glw = {
      canvas: { width: 1920, height: 1080 },
      uniform1f,
      uniform2f,
    };

    (program as any).bindTextures = bindTextures;
    (program as any).bindBufferCollection = bindBufferCollection;
    (program as any).glw = glw;
    (program as any).useTimeValue = false;
    (program as any).useSystemAlpha = false;
    (program as any).useSystemDimensions = false;

    return {
      program,
      bindTextures,
      bindBufferCollection,
      uniform1f,
      uniform2f,
    };
  }

  it('binds an SdfRenderOp using the main framebuffer resolution', () => {
    const {
      program,
      bindTextures,
      bindBufferCollection,
      uniform1f,
      uniform2f,
    } = createProgram();
    const renderOp = {
      isCoreNode: false,
      shader: { uniforms: { hasStoredUniforms: false } },
      renderOpTextures: [],
      quadBufferCollection: {},
      parentHasRenderTexture: false,
      framebufferDimensions: null,
      rtt: false,
      stage: { pixelRatio: 1.5 },
      time: 0,
      worldAlpha: 1,
      w: 100,
      h: 20,
    };

    program.bindRenderOp(renderOp as any);

    expect(bindTextures).toHaveBeenCalledWith(renderOp.renderOpTextures);
    expect(bindBufferCollection).toHaveBeenCalledWith(
      renderOp.quadBufferCollection,
    );
    expect(uniform1f).toHaveBeenCalledWith('u_pixelRatio', 1.5);
    expect(uniform2f).toHaveBeenCalledWith('u_resolution', 1920, 1080);
  });

  it('uses the parent framebuffer resolution for an SdfRenderOp in RTT', () => {
    const { program, uniform1f, uniform2f } = createProgram();
    const renderOp = {
      isCoreNode: false,
      shader: { uniforms: { hasStoredUniforms: false } },
      renderOpTextures: [],
      quadBufferCollection: {},
      parentHasRenderTexture: true,
      framebufferDimensions: { w: 320, h: 180 },
      rtt: false,
      stage: { pixelRatio: 2 },
      time: 0,
      worldAlpha: 1,
      w: 100,
      h: 20,
    };

    program.bindRenderOp(renderOp as any);

    expect(uniform1f).toHaveBeenCalledWith('u_pixelRatio', 1);
    expect(uniform2f).toHaveBeenCalledWith('u_resolution', 320, 180);
  });

  it('uses parent RTT dimensions for core-node render ops', () => {
    const { program, uniform1f, uniform2f } = createProgram();
    const renderOp = {
      framebufferDimensions: { w: 320, h: 180 },
      isCoreNode: true,
      parentFramebufferDimensions: { w: 640, h: 360 },
      parentHasRenderTexture: true,
      quadBufferCollection: {},
      renderOpTextures: [],
      rtt: false,
      shader: {
        uniforms: { hasStoredUniforms: false },
      },
      stage: { pixelRatio: 2 },
      time: 0,
      worldAlpha: 1,
      w: 100,
      h: 20,
    } as any;

    program.bindRenderOp(renderOp);

    expect(uniform1f).toHaveBeenCalledWith('u_pixelRatio', 1.0);
    expect(uniform2f).toHaveBeenCalledWith('u_resolution', 640, 360);
  });
});

describe('WebGlRenderer.canReuseRenderOp', () => {
  it('reuses SDF text render ops with matching parent RTT dimensions', () => {
    const renderer = Object.create(WebGlRenderer.prototype) as WebGlRenderer;
    renderer.stencilDepth = 0;
    const node = makeSdfTextNode();
    node.stencilDepth = 0;

    node.props.shader = {
      shaderKey: 'default',
    } as any;

    renderer.curRenderOp = node;

    expect(renderer.reuseRenderOp(node)).toBe(true);
  });
});
