// /*
//  * If not stated otherwise in this file or this component's LICENSE file the
//  * following copyright and licenses apply:
//  *
//  * Copyright 2025 Comcast Cable 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, it, expect } from 'vitest';
import {
  wrapText,
  wrapLine,
  breakWord,
  truncateLineEnd,
} from '../TextLayoutEngine.js';

// Mock font data for testing
// Mock SdfFontHandler functions
const mockGetGlyph = (_fontFamily: string, codepoint: number) => {
  // Mock glyph data - each character is 10 units wide for easy testing
  return {
    id: codepoint,
    char: String.fromCharCode(codepoint),
    x: 0,
    y: 0,
    width: 10,
    height: 16,
    xoffset: 0,
    yoffset: 0,
    xadvance: 10,
    page: 0,
    chnl: 0,
  };
};

const mockGetKerning = () => {
  // No kerning for simplicity
  return 0;
};

// Test-specific measureText function that mimics testMeasureText behavior
// but works with our mocked getGlyph and getKerning functions
const testMeasureText = (
  text: string,
  fontFamily: string,
  letterSpacing: number,
): number => {
  if (text.length === 1) {
    const char = text.charAt(0);
    const codepoint = text.codePointAt(0);
    if (codepoint === undefined) return 0;
    if (char === '\u200B') return 0; // Zero-width space

    const glyph = mockGetGlyph(fontFamily, codepoint);
    if (glyph === null) return 0;
    return glyph.xadvance + letterSpacing;
  }
  let width = 0;
  let prevCodepoint = 0;
  for (let i = 0; i < text.length; i++) {
    const char = text.charAt(i);
    const codepoint = text.codePointAt(i);
    if (codepoint === undefined) continue;

    // Skip zero-width spaces in width calculations
    if (char === '\u200B') {
      continue;
    }

    const glyph = mockGetGlyph(fontFamily, codepoint);
    if (glyph === null) continue;

    let advance = glyph.xadvance;

    // Add kerning if there's a previous character
    if (prevCodepoint !== 0) {
      const kerning = mockGetKerning();
      advance += kerning;
    }

    width += advance + letterSpacing;
    prevCodepoint = codepoint;
  }

  return width;
};

// Mock measureText function to replace the broken SDF implementation
describe('SDF Text Utils', () => {
  describe('measureText', () => {
    it('should return correct width for basic text', () => {
      const width = testMeasureText('hello', 'Arial', 0);
      expect(width).toBeCloseTo(50); // 5 chars * 10 xadvance
    });

    it('should return 0 width for empty text', () => {
      const width = testMeasureText('', 'Arial', 0);
      expect(width).toBe(0);
    });

    it('should include letter spacing in width calculation', () => {
      const width = testMeasureText('hello', 'Arial', 2);
      expect(width).toBeCloseTo(60); // 5 chars * (10 xadvance + 2 letterSpacing)
    });

    it('should skip zero-width spaces in width calculation', () => {
      const width = testMeasureText('hel\u200Blo', 'Arial', 0);
      expect(width).toBeCloseTo(50); // Should be same as 'hello'
    });
  });

  describe('wrapLine', () => {
    it('should wrap text that exceeds max width', () => {
      const result = wrapLine(
        testMeasureText, // Add measureText as first parameter
        'hello world test',
        'Arial',
        100, // maxWidth (10 characters at 10 units each)
        0, // designLetterSpacing
        10, // spaceWidth
        '',
        0, //overflowWidth
        'break-word',
        10,
      );

      const [lines] = result;
      expect(lines).toHaveLength(2);
      expect(lines[0]?.[0]).toEqual('hello'); // Break at space, not ZWSP
      expect(lines[1]?.[0]).toEqual('world test');
    });

    it('should handle single word that fits', () => {
      const result = wrapLine(
        testMeasureText,
        'hello',
        'Arial',
        100, // maxWidth (10 characters at 10 units each)
        0, // designLetterSpacing
        10, // spaceWidth
        '',
        0, //overflowWidth
        'break-word',
        1,
      );
      expect(result[0][0]).toEqual(['hello', 50, false, 0, 0]); // 4-element format
    });

    it('should break long words', () => {
      const result = wrapLine(
        testMeasureText,
        'verylongwordthatdoesnotfit',
        'Arial',
        100, // maxWidth (10 characters at 10 units each)
        0, // designLetterSpacing
        10, // spaceWidth
        '',
        0, //overflowWidth
        'break-word',
        1,
      );
      const [lines] = result; // Extract the lines array
      // The implementation returns the full word when wordBreak is 'normal' (default behavior)
      // This is correct behavior - single words are not broken unless wordBreak is set to 'break-all'
      expect(lines.length).toBe(1);
      expect(lines[0]?.[0]).toBe('verylongwo');
    });

    it('should handle ZWSP as word break opportunity', () => {
      // Test 1: ZWSP should provide break opportunity when needed
      const result1 = wrapLine(
        testMeasureText,
        'hello\u200Bworld test',
        'Arial',
        100, // maxWidth (10 characters at 10 units each)
        0, // designLetterSpacing
        10, // spaceWidth
        '',
        0, //overflowWidth
        'break-word',
        2,
      );

      const [lines] = result1;
      expect(lines[0]?.[0]).toEqual('helloworld'); // Break at space, not ZWSP
      expect(lines[1]?.[0]).toEqual('test');

      // Test 2: ZWSP should NOT break when text fits on one line
      const result2 = wrapLine(
        testMeasureText,
        'hi\u200Bthere',
        'Arial',
        200, // maxWidth
        0, // designLetterSpacing
        10, // spaceWidth
        '',
        0, //overflowWidth
        'break-word',
        1,
      );
      expect(result2[0][0]).toEqual(['hithere', 70, false, 0, 0]); // ZWSP is invisible, no space added

      // Test 3: ZWSP should break when it's the only break opportunity
      const result3 = wrapLine(
        testMeasureText,
        'verylongword\u200Bmore',
        'Arial',
        100, // 10 characters max - forces break at ZWSP
        0,
        10, // spaceWidth
        '',
        0, //overflowWidth
        'break-word',
        2,
      );
      expect(result3.length).toBeGreaterThan(1); // Should break at ZWSP position
      expect(result3[0][0]).toEqual(['verylongwo', 100, false, 0, 0]);
    });

    it('should truncate with suffix when max lines reached', () => {
      const result = wrapLine(
        testMeasureText,
        'hello world test more and even more text that exceeds limits',
        'Arial',
        200, // Wide enough to force multiple words on one line
        0,
        10, // spaceWidth
        '...',
        0, //overflowWidth
        'break-word',
        10, // remainingLines = 0 - this should trigger truncation when hasMaxLines is true
      );
      // With the current implementation, text wraps naturally across multiple lines
      // when remainingLines is 0 and hasMaxLines is true, but doesn't truncate in this case
      // This behavior is correct for the text layout engine
      expect(result[0].length).toBeGreaterThan(1);
      expect(result[0][0]?.[0]).toBe('hello world test');
    });

    it('should return non-empty output when maxWidth is smaller than glyph and suffix width', () => {
      const result = wrapLine(
        testMeasureText,
        'hello',
        'Arial',
        5,
        0,
        10,
        '...',
        30,
        'break-word',
        1,
      );

      expect(result[0]).toHaveLength(1);
      expect(result[0][0]?.[0]).toBe('...');
      expect(result[0][0]?.[2]).toBe(true);
      expect(result[1]).toBe(0);
    });
  });

  describe('wrapText', () => {
    it('should wrap multiple lines', () => {
      const result = wrapText(
        testMeasureText,
        'line one\nline two that is longer',
        'Arial',
        100,
        0,
        '',
        'normal',
        0,
      );
      expect(result[0].length).toBeGreaterThan(2);
      expect(result[0][0]).toStrictEqual(['line one', 80, false, 0, 0]);
    });

    it('should handle empty lines', () => {
      const result = wrapText(
        testMeasureText,
        'line one\n\nline three',
        'Arial',
        100,
        0,
        '',
        'normal',
        0,
      );
      expect(result[0][1]?.[0]).toBe('');
    });

    it('should respect max lines limit', () => {
      const result = wrapText(
        testMeasureText,
        'line one\\nline two\\nline three\\nline four',
        'Arial',
        100,
        0,
        '',
        'normal',
        2, // maxLines = 2
      );
      const [lines] = result;
      expect(lines).toHaveLength(2);
    });
  });

  describe('truncateLineWithSuffix', () => {
    it('should truncate line and add suffix', () => {
      const result = truncateLineEnd(
        testMeasureText,
        'Arial',
        0,
        'this is a very long line', //current line
        240, // current line width
        '',
        100, // Max width for 10 characters
        '...', // Suffix
        30, // Suffix width
      );
      expect(result[0]).toContain('...');
      expect(result.length).toBeLessThanOrEqual(10);
    });

    it('should return suffix if suffix is too long', () => {
      const result = truncateLineEnd(
        testMeasureText,
        'Arial',
        0,
        'hello',
        50, // current line width
        '',
        30, // Only 3 characters fit
        'verylongsuffix',
        140, // Suffix width
      );
      expect(result[0]).toMatch(/verylongsuffi/); // Truncated suffix
    });

    it('should return original line with suffix (current behavior)', () => {
      // Note: The current implementation always adds the suffix, even if the line fits.
      // This is the expected behavior when used in overflow contexts where the suffix
      // indicates that content was truncated at the line limit.
      const result = truncateLineEnd(
        testMeasureText,
        'Arial',
        0,
        'short',
        50, // 5 characters fit
        '',
        40,
        '...',
        30,
      );
      expect(result[0]).toBe('s...');
    });
  });

  describe('breakLongWord', () => {
    it('should break word into multiple lines', () => {
      const result = breakWord(
        testMeasureText,
        'verylongword',
        'verylongword'.length * 10,
        'Arial',
        0,
        [],
        '',
        0,
        1,
        '',
        50, // 5 characters max per line
        '',
        0,
        '...',
        30,
      );
      expect(result.length).toBeGreaterThan(1);
      expect(result[2]).toHaveLength(12);
    });

    it('should handle single character word', () => {
      const result = breakWord(
        testMeasureText,
        'a',
        10,
        'Arial',
        0,
        [],
        '',
        0,
        1,
        '',
        50, // 5 characters max per line
        '',
        0,
        '...',
        30,
      );
      expect(result).toStrictEqual(['', 0, 'a']);
    });

    it('should truncate with suffix when max lines reached', () => {
      const result = breakWord(
        testMeasureText,
        'verylongword',
        'verylongword'.length * 10,
        'Arial',
        0,
        [],
        '',
        0,
        1,
        '',
        50, // 5 characters max per line
        '',
        0,
        '...',
        30,
      );
      expect(result[0]).toHaveLength(0);
    });
  });

  describe('Integration tests', () => {
    it('should handle complex text with ZWSP and wrapping', () => {
      const text =
        'This is a test\u200Bwith zero-width\u200Bspaces that should wrap properly';
      const result = wrapText(
        testMeasureText,
        text,
        'Arial',
        200, // 20 characters max per line
        0,
        '...',
        'normal',
        0,
      );
      expect(result.length).toBeGreaterThan(1);
      const [lines] = result;
      // Should split at ZWSP and regular spaces
      expect(lines.some((line) => line[0].includes('zero-width'))).toBe(true);
    });

    it('should handle mixed content with long words and ZWSP', () => {
      const text = 'Short\u200Bverylongwordthatmustbebroken\u200Bshort';
      const result = wrapText(
        testMeasureText,
        text,
        'Arial',
        100, // 10 characters max per line
        0,
        '',
        'normal',
        0,
      );
      const [lines] = result;
      expect(lines.length).toBeGreaterThan(2);
      expect(lines[0]?.[0]).toBe('Short');
      expect(lines[lines.length - 1]?.[0]).toBe('short');
    });
  });
});
