/** @vitest-environment jsdom */
import { describe, it, expect, vi } from 'vitest';
import { CARD_SIZES, cardLayout, fitPhoto, drawShareCard, stampAiGeneratedWatermark, fitText, wrapTwoLines, drawSpacedText, type CardRect } from './shareCard';
import { context } from './fixtures/canvasContext';

const photo = { naturalWidth: 1600, naturalHeight: 900 } as HTMLImageElement;
const metadata = { itemName: 'Linen Overshirt', brandName: 'Atelier Nord', productUrl: 'https://forbidden.test' };
const inside = (inner: CardRect, outer: CardRect) => {
  expect(inner.x).toBeGreaterThanOrEqual(outer.x - 1e-8);
  expect(inner.y).toBeGreaterThanOrEqual(outer.y - 1e-8);
  expect(inner.x + inner.width).toBeLessThanOrEqual(outer.x + outer.width + 1e-8);
  expect(inner.y + inner.height).toBeLessThanOrEqual(outer.y + outer.height + 1e-8);
};
const intersection = (a: CardRect, b: CardRect) => Math.max(0, Math.min(a.x+a.width,b.x+b.width)-Math.max(a.x,b.x)) * Math.max(0, Math.min(a.y+a.height,b.y+b.height)-Math.max(a.y,b.y));

describe('Polaroid geometry and actual canvas operations', () => {
  for (const size of [...Object.values(CARD_SIZES), { width: 540, height: 960 }, { width: 1080, height: 1080 }, { width: 1920, height: 1080 }]) {
    for (const [iw, ih] of [[1080,1920], [602,1024], [1600,900], [900,900], [100,4000], [4000,100]]) {
      it(`${size.width}x${size.height}: whole ${iw}x${ih} image, separate UI, final disclosure`, () => {
        const ctx = context(size.width, size.height);
        const image = { naturalWidth: iw, naturalHeight: ih } as HTMLImageElement;
        const layout = cardLayout(size), canvas = { x: 0, y: 0, ...size };
        const ui = [layout.caption, ...layout.chips, layout.tag, layout.strip, layout.watermark];
        for (const r of [layout.photo, ...ui]) inside(r, canvas);
        for (const r of ui) expect(intersection(r, layout.photo)).toBe(0);
        for (let i = 0; i < ui.length; i++) for (let j = i+1; j < ui.length; j++) expect(intersection(ui[i], ui[j])).toBe(0);
        drawShareCard(ctx, image, null, metadata);
        stampAiGeneratedWatermark(ctx);
        // Inspect the actual draw, not just the layout helper: five arguments mean
        // the entire source is used (the nine-argument overload can crop it).
        const draw = vi.mocked(ctx.drawImage).mock.calls[0];
        expect(draw).toHaveLength(5); expect(draw[0]).toBe(image);
        const [, x, y, w, h] = draw as unknown as [HTMLImageElement, number, number, number, number];
        inside({ x, y, width: w, height: h }, layout.photo);
        expect(w / iw).toBeCloseTo(h / ih, 10);
        expect(w / iw).toBeLessThanOrEqual(Math.min(layout.photo.width / iw, layout.photo.height / ih) + 1e-8);
        expect({ x, y, width: w, height: h }).toEqual(fitPhoto(image, layout.photo));
        expect(ctx.fillRect).toHaveBeenCalledWith(0, 0, size.width, size.height);
        expect(ctx.roundRect).toHaveBeenCalledWith(layout.strip.x,layout.strip.y,layout.strip.width,layout.strip.height,28*layout.scale);
        const texts = vi.mocked(ctx.fillText).mock.calls;
        expect(texts.find(c=>c[0]==='Should I get it?')?.slice(1)).toEqual([layout.caption.x,layout.caption.y]);
        const words = texts.map(c=>c[0]);
        for (const word of ['Should I get it?','❤️','yes','😢','no','powered by','aisthetix','Try it on your own photo','ai generated']) expect(words).toContain(word);
        expect(words.join(' ')).not.toMatch(/https:|forbidden|created with|virtual try-on|variant|QR|\$/);
        expect(texts.at(-1)?.[0]).toBe('ai generated');
        expect(texts.at(-1)?.[1]).toBeCloseTo(size.width - 24*layout.scale, 8);
        expect(texts.at(-1)?.[2]).toBeCloseTo(size.height - 16*layout.scale, 8);
        const lastDraw = Math.max(...['drawImage','fillRect','fill','stroke','fillText','restore'].flatMap(k=>vi.mocked((ctx as any)[k]).mock.invocationCallOrder));
        expect(vi.mocked(ctx.fillText).mock.invocationCallOrder.at(-1)).toBe(lastDraw);
      });
    }
  }
  it('fits long identity into two lines and uses the product thumbnail', () => {
    const ctx=context(); const thumb={...photo} as HTMLImageElement;
    drawShareCard(ctx,photo,thumb,{...metadata,itemName:'A very long linen overshirt with many extra words in its name',brandName:'Extremely long merchant brand name'});
    const texts=vi.mocked(ctx.fillText).mock.calls;
    expect(texts.find(c=>c[0].startsWith('EXTREMELY'))?.[0]).toMatch(/…$/);
    const names=texts.filter(c=>c[1]===168 && !c[0].startsWith('EXTREMELY'));
    expect(names).toHaveLength(2); expect(names[1][0]).toMatch(/…$/);
    expect(ctx.drawImage).toHaveBeenCalledTimes(2);
    expect(vi.mocked(ctx.drawImage).mock.calls[1][0]).toBe(thumb);
  });
  it('omits empty brand/item and draws a placeholder when no thumbnail loads', () => {
    const ctx=context(); drawShareCard(ctx,photo,null,{itemName:'Shirt',brandName:' ',productUrl:null});
    expect(vi.mocked(ctx.fillText).mock.calls.filter(c=>c[1]===168)).toHaveLength(1);
    expect(ctx.lineTo).toHaveBeenCalled();
    const empty=context(); drawShareCard(empty,photo,null,{itemName:' ',brandName:'BRAND',productUrl:null});
    expect(vi.mocked(empty.fillText).mock.calls.some(c=>c[0]==='BRAND')).toBe(false);
    expect(empty.lineTo).not.toHaveBeenCalled();
  });
  it('rejects invalid canvases and fits images with width/height only', () => {
    for (const size of [{width:0,height:1920},{width:1080,height:-1},{width:NaN,height:1920},{width:1080,height:Infinity}]) expect(()=>cardLayout(size)).toThrow('invalid canvas');
    expect(fitPhoto({width:100,height:200} as HTMLImageElement,{x:0,y:0,width:100,height:100})).toEqual({x:25,y:0,width:50,height:100});
  });
  it('fits one/two lines, ellipsis, hard words, and spaces glyphs without letterSpacing', () => {
    const ctx=context(); expect(fitText(ctx,'Short',304)).toBe('Short');
    expect(wrapTwoLines(ctx,'one two',120)).toEqual(['one two']);
    expect(wrapTwoLines(ctx,'one two three',120)).toEqual(['one two','three']);
    expect(wrapTwoLines(ctx,'one two three four five',120)).toEqual(['one two','three…']);
    expect(wrapTwoLines(ctx,'abcdefghijklmnopqrstuvwxyz',120)).toEqual(['abcdef…']);
    delete (ctx as any).letterSpacing;
    drawSpacedText(ctx,'AB',706,1020,3.6);
    expect(ctx.fillText).toHaveBeenCalledWith('A',706,1020);expect(ctx.fillText).toHaveBeenCalledWith('B',726.6,1020);
    expect(()=>drawShareCard(ctx,{width:0,height:0} as HTMLImageElement,null,{itemName:'',productUrl:null})).toThrow('invalid image dimensions');
  });
});
