/*
 * If not stated otherwise in this file or this component's LICENSE file the
 * following copyright and licenses apply:
 *
 * Copyright 2023 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 type { CoreTextNode, CoreTextNodeProps } from '../CoreTextNode.js';
import type { WebGlCtxTexture } from '../renderers/webgl/WebGlCtxTexture.js';
import type { Stage } from '../Stage.js';

// Text baseline and vertical align types
export type TextBaseline =
  | 'alphabetic'
  | 'hanging'
  | 'middle'
  | 'ideographic'
  | 'bottom';
export type TextVerticalAlign = 'top' | 'middle' | 'bottom';
export type TextRenderers = 'canvas' | 'sdf';
/**
 * Structure mapping font family names to a set of font faces.
 */
export interface FontFamilyMap {
  [familyName: string]: FontFace;
}

/**
 * Font metrics used for layout and default line height calculations.
 */
export interface FontMetrics {
  /**
   * The distance, in font units, from the baseline to the highest point of the font.
   */
  ascender: number;
  /**
   * The distance, in font units, from the baseline to the lowest point of the font.
   */
  descender: number;
  /**
   * The additional space used in the calculation of the default line height in font units.
   */
  lineGap: number;
  /**
   * The number of font units per 1 EM.
   */
  unitsPerEm: number;
}

/**
 * Normalized font metrics where values are expressed as a fraction of 1 EM.
 */
export interface NormalizedFontMetrics {
  /**
   * The distance, as a fraction of 1 EM, from the baseline to the highest point of the font.
   */
  ascender: number;
  /**
   * The distance, as a fraction of 1 EM, from the baseline to the lowest point of the font.
   */
  descender: number;
  /**
   * The additional space used in the calculation of the default line height as a fraction of 1 EM
   */
  lineGap: number;
}

/**
 * Text renderer properties that are used in resolving appropriate font faces
 *
 * @remarks
 * Extended by {@link TrProps}
 */
export interface TrFontProps {
  /**
   * Font Family
   *
   * @internalRemarks
   * `fontFamily` is defined currently as single string, but in the future we may want to
   * support multiple font family fallbacks, as this is supported by CSS / Canvas2d. We can
   * do this in a backwards compatible way by unioning an array of strings to the
   * `fontFamily` property.
   */
  fontFamily: string;
  /**
   * Font Style
   *
   * @remarks
   * The font style to use when looking up the font face. This can be one of the
   * following strings:
   * - `'normal'`
   * - `'italic'`
   * - `'oblique'`
   */
  fontStyle: 'normal' | 'italic' | 'oblique';
  /**
   * Font Size
   *
   * @remarks
   * The font size to use when looking up the font face.
   *
   * The font size is specified in pixels and is the height of the font's
   * em-square. The em-square is essentially the height of the capital letters
   * for the font. The actual height of the text can be larger than the
   * specified font size, as the font may have ascenders and descenders that
   * extend beyond the em-square.
   *
   * @default 16
   */
  fontSize: number;
}

export interface TrProps extends TrFontProps {
  /**
   * Text to display
   *
   * @default ''
   */
  text: string;
  /**
   * Text alignment
   *
   * @remarks
   * Alignment of the text relative to it's contained bounds. For best results,
   * use {@link contain} mode `'width'` or `'both'` and a set an explicit
   * {@link width} for the text to be aligned within.
   *
   * @default 'left'
   */
  textAlign: 'left' | 'center' | 'right';
  /**
   * Color of text
   *
   * @remarks
   * The color value is a number in the format 0xRRGGBBAA, where RR is the red
   * component, GG is the green component, BB is the blue component, and AA is
   * the alpha component.
   *
   * @default 0xffffffff (opaque white)
   */
  color: number;
  x: number;
  y: number;

  maxWidth: number;
  maxHeight: number;
  /**
   * Vertical offset for text
   *
   * @remarks
   * The vertical offset of the text.
   *
   * @default 0
   */
  offsetY: number;
  /**
   * Letter spacing for text (in pixels)
   *
   * @remarks
   * This property sets additional (or reduced, if value is negative) spacing
   * between characters in the text.
   *
   * @default 0
   */
  letterSpacing: number;
  /**
   * Line height for text (in pixels)
   *
   * @remarks
   * This property sets the height of each line. If set to `undefined`, the
   * line height will be calculated based on the font and font size to be the
   * minimal height required to completely contain a line of text.
   *
   * See: https://github.com/lightning-js/renderer/issues/170
   *
   * @default `undefined`
   */
  lineHeight: number;
  /**
   * Max lines for text
   *
   * @remarks
   * This property sets max number of lines of a text paragraph.
   * Not yet implemented in the SDF renderer.
   *
   * @default 0
   */
  maxLines: number;
  /**
   * Vertical Align for text when lineHeight > fontSize
   *
   * @remarks
   * This property sets the vertical align of the text.
   * Not yet implemented in the SDF renderer.
   *
   * @default middle
   */
  verticalAlign: TextVerticalAlign;
  /**
   * Overflow Suffix for text
   *
   * @remarks
   * The suffix to be added when text is cropped due to overflow.
   * Not yet implemented in the SDF renderer.
   *
   * @default "..."
   */
  overflowSuffix: string;

  /**
   * Word Break for text
   *
   * @remarks
   * This property sets how words should break when reaching the end of a line.
   *
   * - `'overflow'`: Uses the Css/HTML normal word-break behavior, generally not used in app development.
   * - `'break-all'`: To prevent overflow, word breaks should happen between any two characters.
   * - `'break-word'`: To prevent overflow, word breaks should happen between words. If words are too long word breaks happen between any two characters.
   *
   * @default "break-word"
   */
  wordBreak: 'overflow' | 'break-all' | 'break-word';

  /**
   * contain mode for text
   *
   * @remarks
   *
   * This property sets how the text should be contained within its bounding box.
   *
   * - 'width': The text is contained within the specified maxWidth, horizontal position of text will adjust according to {@link textAlign}.
   * - 'height': The text is contained within the specified maxHeight, vertical position of text will adjust according to {@link verticalAlign}.
   * - 'both': The text is contained within both the specified maxWidth and maxHeight.
   * - 'none': The text is not contained within any bounding box.
   *
   * @default 'none'
   */
  contain: 'width' | 'height' | 'both' | 'none';
}

/**
 * Glyph layout information for WebGL rendering
 */
export interface GlyphLayout {
  /**
   * X position relative to text origin
   */
  x: number;
  /**
   * Y position relative to text origin
   */
  y: number;
  /**
   * Width of glyph in font units
   */
  width: number;
  /**
   * Height of glyph in font units
   */
  height: number;
  /**
   * Atlas texture coordinates (normalized 0-1)
   */
  atlasX: number;
  atlasY: number;
  atlasWidth: number;
  atlasHeight: number;
}

/**
 * Complete text layout information for caching
 */
export interface TextLayout {
  /**
   * vertices for rendering quads in WebGL
   */
  vertexBuffer: Float32Array;
  /**
   * glyph count in layout
   */
  glyphCount: number;
  /**
   * Total text width
   */
  width: number;
  /**
   * Total text height
   */
  height: number;
  /**
   * Font scale factor
   */
  fontScale: number;
  /**
   * Line height
   */
  lineHeight: number;
  /**
   * Font family used
   */
  fontFamily: string;
  /**
   * distanceRange used
   */
  distanceRange: number;
  /**
   * number of lines that exceeded maxHeight and were truncated
   */
  remainingLines: number;
  /**
   * Whether there is remaining text that exceeded maxHeight and was truncated
   */
  hasRemainingText: boolean;
}

export interface FontLoadOptions {
  fontFamily: string;
  metrics?: FontMetrics;
  // For Canvas/traditional font loading
  fontUrl?: string;
  // For SDF/atlas-based font loading
  atlasUrl?: string;
  atlasDataUrl?: string;
}

/**
 * Measure Width of Text function to be defined in font handlers, used in TextLayoutEngine
 */
export type MeasureTextFn = (
  text: string,
  fontFamily: string,
  letterSpacing: number,
) => number;

export interface FontHandler {
  init: (
    c: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
  ) => void;
  type: 'canvas' | 'sdf';
  isFontLoaded: (fontFamily: string) => boolean;
  loadFont: (stage: Stage, options: FontLoadOptions) => Promise<void>;
  waitingForFont: (fontFamily: string, CoreTextNode) => void;
  stopWaitingForFont: (fontFamily: string, CoreTextNode) => void;
  getFontFamilies: () => FontFamilyMap;
  canRenderFont: (trProps: TrProps) => boolean;
  getFontMetrics: (
    fontFamily: string,
    fontSize: number,
  ) => NormalizedFontMetrics;
  measureText: MeasureTextFn;
}

export interface TextRenderProps {
  fontFamily: string;
  fontSize: number;
  color: number;
  offsetY: number;
  worldAlpha: number;
  globalTransform: Float32Array;
  clippingRect: unknown;
  width: number;
  height: number;
  parentHasRenderTexture: boolean;
  framebufferDimensions: unknown;
  stage: Stage;
  /**
   * Mutable wrapper ref used by the SDF renderer to cache the underlying
   * WebGLBuffer across frames. The SDF renderer reads and writes the
   * `.current` property so the node's ref box is updated in-place.
   * CoreTextNode owns the ref and is responsible for calling
   * deleteBuffer when the buffer is no longer needed.
   */
  glBufferRef: { current: WebGLBuffer | null };
}

export interface RenderInfo {
  width: number;
  height: number;
  hasRemainingText: boolean;
  remainingLines: number;
}

export type SdfRenderInfo = RenderInfo & {
  type: 'sdf';
  layout: TextLayout;
  atlasTexture: WebGlCtxTexture;
};

export type CanvasRenderInfo = RenderInfo & {
  type: 'canvas';
  imageData: ImageData;
};

export type TextRenderInfo = SdfRenderInfo | CanvasRenderInfo;

export interface TextRenderer {
  type: 'canvas' | 'sdf';
  font: FontHandler;
  renderText: (props: CoreTextNodeProps) => TextRenderInfo;
  renderQuads: (textNode: CoreTextNode) => void;
  init: (stage: Stage) => void;
  clearCache: () => void;
}

/**
 * Text line struct for text mapping
 * 0 - text
 * 1 - width
 * 2 - truncated
 * 3 - line offset x
 * 4 - line offset y
 */
export type TextLineStruct = [string, number, boolean, number, number];

/**
 * Wrapped lines struct for text mapping
 * 0 - line structs
 * 1 - remaining lines
 * 2 - remaining text
 */
export type WrappedLinesStruct = [TextLineStruct[], number, boolean];

/**
 * Wrapped lines struct for text mapping
 * 0 - line structs
 * 1 - remaining lines
 * 2 - remaining text
 * 3 - bare line height
 * 4 - line height pixels
 * 5 - effective width
 * 6 - effective height
 */
export type TextLayoutStruct = [
  TextLineStruct[],
  number,
  boolean,
  number,
  number,
  number,
  number,
];
