// Copyright (C) 2026 The Android Open Source Project
//
// 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.

// Signed Distance Field (SDF) generation for closed polygons.
// SDFs enable resolution-independent rendering of shapes with smooth anti-aliasing.

import type {Point2D} from './../geom';

// Signed distance from point to line segment
function sdSegment(p: Point2D, a: Point2D, b: Point2D): number {
  const pax = p.x - a.x;
  const pay = p.y - a.y;
  const bax = b.x - a.x;
  const bay = b.y - a.y;
  const h = Math.max(
    0,
    Math.min(1, (pax * bax + pay * bay) / (bax * bax + bay * bay)),
  );
  const dx = pax - bax * h;
  const dy = pay - bay * h;
  return Math.sqrt(dx * dx + dy * dy);
}

// Determine if point is inside a closed polygon using ray casting
function isInsidePolygon(p: Point2D, vertices: readonly Point2D[]): boolean {
  let inside = false;
  const n = vertices.length;

  for (let i = 0, j = n - 1; i < n; j = i++) {
    const vi = vertices[i];
    const vj = vertices[j];

    // Ray casting: count edge crossings to the right of the point
    if (
      vi.y > p.y !== vj.y > p.y &&
      p.x < ((vj.x - vi.x) * (p.y - vi.y)) / (vj.y - vi.y) + vi.x
    ) {
      inside = !inside;
    }
  }

  return inside;
}

// Signed distance from point to closed polygon boundary
// Negative inside, positive outside
function sdPolygon(p: Point2D, vertices: readonly Point2D[]): number {
  const n = vertices.length;
  if (n < 3) return Infinity;

  // Find minimum distance to any edge
  let minDist = Infinity;
  for (let i = 0; i < n; i++) {
    const a = vertices[i];
    const b = vertices[(i + 1) % n];
    minDist = Math.min(minDist, sdSegment(p, a, b));
  }

  // Determine sign based on inside/outside
  const inside = isInsidePolygon(p, vertices);
  return inside ? -minDist : minDist;
}

/**
 * Generate a signed distance field for a closed polygon.
 *
 * @param vertices - Array of vertices defining the polygon in normalized 0-1 coordinates
 * @param size - Size of the output texture (size x size pixels)
 * @param spread - How much distance (in normalized coords) maps to the 0-1 range.
 *                 Larger values = more gradual falloff, smaller = sharper edges.
 *                 Default 0.1 works well for most shapes.
 * @returns Uint8Array of RGBA data (size * size * 4 bytes) where alpha channel
 *          contains the SDF: 0.5 = edge, <0.5 = inside, >0.5 = outside
 */
export function generatePolygonSDF(
  vertices: readonly Point2D[],
  size: number,
  spread: number = 0.1,
): Uint8Array {
  const data = new Uint8Array(size * size * 4);

  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      // Map pixel to normalized coordinates (0-1), sampling at pixel centers
      const p: Point2D = {
        x: (x + 0.5) / size,
        y: (y + 0.5) / size,
      };

      // Get signed distance (negative inside, positive outside)
      const dist = sdPolygon(p, vertices);

      // Normalize to 0-1 range: 0.5 = edge, <0.5 = inside, >0.5 = outside
      const normalized = Math.max(0, Math.min(1, dist / spread + 0.5));

      const idx = (y * size + x) * 4;
      data[idx + 0] = 255; // R
      data[idx + 1] = 255; // G
      data[idx + 2] = 255; // B
      data[idx + 3] = Math.round(normalized * 255); // A = SDF value
    }
  }

  return data;
}

/**
 * Create a WebGL texture from SDF data.
 *
 * @param gl - WebGL2 rendering context
 * @param sdfData - RGBA data from generatePolygonSDF
 * @param size - Size of the texture (must match the size used in generatePolygonSDF)
 * @returns WebGL texture configured for SDF rendering
 */
export function createSDFTexture(
  gl: WebGL2RenderingContext,
  sdfData: Uint8Array,
  size: number,
): WebGLTexture {
  const texture = gl.createTexture()!;
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.RGBA,
    size,
    size,
    0,
    gl.RGBA,
    gl.UNSIGNED_BYTE,
    sdfData,
  );

  // Linear filtering is essential for SDF - it interpolates distance values smoothly
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

  return texture;
}
