import { describe, expect, it } from 'vitest';
import { Line } from './Line';
import { getFabricDocument } from '../../fabric';
import { FabricObject } from './Object/Object';
import { createReferenceObject } from '../../test/utils';

describe('Line', () => {
  const LINE_OBJECT = createReferenceObject('Line', {
    left: 12,
    top: 13,
    width: 2,
    height: 2,
    x1: -1,
    y1: -1,
    x2: 1,
    y2: 1,
  });

  it('initializes constructor correctly', () => {
    expect(Line).toBeTruthy();
    const line = new Line([10, 11, 20, 21]);

    expect(line).toBeInstanceOf(Line);
    expect(line).toBeInstanceOf(FabricObject);

    expect(line.constructor).toHaveProperty('type', 'Line');

    expect(line.get('x1')).toBe(10);
    expect(line.get('y1')).toBe(11);
    expect(line.get('x2')).toBe(20);
    expect(line.get('y2')).toBe(21);

    const lineWithoutPoints = new Line();

    expect(lineWithoutPoints.get('x1')).toBe(0);
    expect(lineWithoutPoints.get('y1')).toBe(0);
    expect(lineWithoutPoints.get('x2')).toBe(0);
    expect(lineWithoutPoints.get('y2')).toBe(0);
  });

  it('has complexity function', () => {
    const line = new Line();
    expect(line.complexity).toBeTypeOf('function');
  });

  it('generates SVG correctly', () => {
    const line = new Line([11, 12, 13, 14]);
    const EXPECTED_SVG =
      '<g transform="matrix(1 0 0 1 12 13)"  >\n<line style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;"  x1="-1" y1="-1" x2="1" y2="1" />\n</g>\n';
    expect(line.toSVG()).toEqual(EXPECTED_SVG);
  });

  it('converts to object correctly', () => {
    const line = new Line([11, 12, 13, 14]);
    expect(line.toObject).toBeTypeOf('function');
    expect(line.toObject()).toEqual(LINE_OBJECT);
  });

  it('creates from object correctly', async () => {
    expect(Line.fromObject).toBeTypeOf('function');
    const line = await Line.fromObject(LINE_OBJECT);
    expect(line).toBeInstanceOf(Line);
    expect(line.toObject()).toEqual(LINE_OBJECT);
  });

  it('creates from SVG element correctly', async () => {
    expect(Line.fromElement).toBeTypeOf('function');

    const namespace = 'http://www.w3.org/2000/svg';
    // TODO: should fromElement also accept SVGElement since test is doing it?
    const lineEl = getFabricDocument().createElementNS(
      namespace,
      'line',
    ) as unknown as HTMLElement;
    const x1 = 11;
    const y1 = 23;
    const x2 = 34;
    const y2 = 7;
    const stroke = 'ff5555';
    const strokeWidth = 2;
    const strokeDashArray = [5, 2];
    const strokeLineCap = 'round';
    const strokeLineJoin = 'bevel';
    const strokeMiterLimit = 5;

    lineEl.setAttributeNS(namespace, 'x1', String(x1));
    lineEl.setAttributeNS(namespace, 'x2', String(x2));
    lineEl.setAttributeNS(namespace, 'y1', String(y1));
    lineEl.setAttributeNS(namespace, 'y2', String(y2));
    lineEl.setAttributeNS(namespace, 'stroke', stroke);
    lineEl.setAttributeNS(namespace, 'stroke-width', String(strokeWidth));
    lineEl.setAttributeNS(namespace, 'stroke-dasharray', '5, 2');
    lineEl.setAttributeNS(namespace, 'stroke-linecap', strokeLineCap);
    lineEl.setAttributeNS(namespace, 'stroke-linejoin', strokeLineJoin);
    lineEl.setAttributeNS(
      namespace,
      'stroke-miterlimit',
      String(strokeMiterLimit),
    );

    const oLine = await Line.fromElement(lineEl);
    expect(oLine).toBeInstanceOf(Line);

    expect(oLine.get('x1')).toBe(x1);
    expect(oLine.get('y1')).toBe(y1);
    expect(oLine.get('x2')).toBe(x2);
    expect(oLine.get('y2')).toBe(y2);
    expect(oLine.get('stroke')).toBe(stroke);
    expect(oLine.get('strokeWidth')).toBe(strokeWidth);
    expect(oLine.get('strokeDashArray')).toEqual(strokeDashArray);
    expect(oLine.get('strokeLineCap')).toBe(strokeLineCap);
    expect(oLine.get('strokeLineJoin')).toBe(strokeLineJoin);
    expect(oLine.get('strokeMiterLimit')).toBe(strokeMiterLimit);

    const lineElWithMissingAttributes = getFabricDocument().createElementNS(
      namespace,
      'line',
    ) as unknown as HTMLElement;
    lineElWithMissingAttributes.setAttributeNS(namespace, 'x1', String(10));
    lineElWithMissingAttributes.setAttributeNS(namespace, 'y1', String(20));

    const oLine2 = await Line.fromElement(lineElWithMissingAttributes);
    expect(oLine2.get('x2')).toBe(0);
    expect(oLine2.get('y2')).toBe(0);
  });

  it('allows straight lines to have 0 width or height', () => {
    const line1 = new Line([10, 10, 100, 10]);
    const line2 = new Line([10, 10, 10, 100]);

    expect(line1.get('height')).toBe(0);
    expect(line2.get('width')).toBe(0);
  });

  it('updates width/height when changing x/y coordinates', () => {
    const line = new Line([50, 50, 100, 100]);

    expect(line.width).toBe(50);

    line.set({ x1: 75, y1: 75, x2: 175, y2: 175 });

    expect(line.width).toBe(100);
    expect(line.height).toBe(100);
  });

  it('parses stroke width from style attribute', async () => {
    const namespace = 'http://www.w3.org/2000/svg';
    const lineEl = getFabricDocument().createElementNS(
      namespace,
      'line',
    ) as unknown as HTMLElement;
    lineEl.setAttribute('style', 'stroke-width:4');

    const oLine = await Line.fromElement(lineEl);
    expect(oLine.strokeWidth).toBe(4);
  });

  describe('line positioning', () => {
    (['left', 'center', 'right'] as const).forEach((originX) => {
      (['top', 'center', 'bottom'] as const).forEach((originY) => {
        [0, 7].forEach((strokeWidth) => {
          [0, 33, 90].forEach((angle) => {
            (['butt', 'round', 'square'] as const).forEach((strokeLineCap) => {
              it(`positions on center regardless of strokeWidth or origin (${originX}/${originY} stroke:${strokeWidth} angle:${angle} cap:${strokeLineCap})`, () => {
                const line = new Line([1, 1, 15, 7], {
                  strokeWidth,
                  originX,
                  originY,
                  angle,
                  strokeLineCap,
                });
                const center = line.getCenterPoint();
                expect(Math.round(center.x)).toBe(8);
                expect(Math.round(center.y)).toBe(4);
              });
            });
          });
        });
      });
    });
  });
});
