/** @vitest-environment jsdom */
import { afterEach, describe, it, expect, vi } from 'vitest';
import * as card from './shareCard';
import { context } from './fixtures/canvasContext';
import cases from './fixtures/share-message-cases.json';
import { composeShareImage, buildShareMessage, shareClipboardText, shareFile, detectShareCapability, detectSharePayloadMode, openShareSheet, copyText, downloadShareImage, isMobileShareDevice, type ShareChannel } from './share';
const metadata={itemName:'Shirt',brandName:'Store',productUrl:'https://shop.test/p?variant=7'};
afterEach(()=>{vi.restoreAllMocks();vi.unstubAllGlobals();vi.useRealTimers();});
for (const fixture of cases) for (const channel of ['whatsapp_status','whatsapp_message','instagram_feed','instagram_stories'] as ShareChannel[]) {
  it(`${fixture.name}: ${channel} exact message`,()=>{
    const expected=channel.startsWith('instagram_')?fixture.instagram:fixture.whatsapp;
    expect(buildShareMessage(fixture.metadata,channel)).toBe(expected);
    expect(shareClipboardText(fixture.metadata,channel)).toBe(expected);
  });
}
function imageMock(broken = '') {
  const loaded: any[]=[];
  class ImageMock {
    naturalWidth=1080;naturalHeight=1920;crossOrigin='';decoding='';onload=()=>{};onerror=()=>{};
    set src(value:string){loaded.push({value,crossOrigin:this.crossOrigin});if(value.includes(broken) && broken)this.onerror();else this.onload();}
  }
  vi.stubGlobal('Image',ImageMock);return loaded;
}
function canvasMock(exporter: (cb:BlobCallback)=>void = cb=>cb(new Blob(['CARD'],{type:'image/png'}))) {
  const ctx=context();const canvas={width:0,height:0,getContext:()=>ctx,toBlob:vi.fn(exporter)};
  Object.defineProperty(ctx, 'canvas', { value: canvas });
  vi.spyOn(document,'createElement').mockReturnValue(canvas as any);return {canvas,ctx};
}
describe('safe composition',()=>{
  it('draws one 1080x1920 PNG and the watermark last; resolves protocol-relative thumbnail with CORS',async()=>{
    const loaded=imageMock();const {canvas}=canvasMock();const draw=vi.spyOn(card,'drawShareCard'),stamp=vi.spyOn(card,'stampAiGeneratedWatermark');
    const blob=await composeShareImage('data:image/png;base64,RAW',{...metadata,productImage:'//cdn.test/garment.png'});
    expect(blob.type).toBe('image/png');expect([canvas.width,canvas.height]).toEqual([1080,1920]);
    expect(draw.mock.invocationCallOrder[0]).toBeLessThan(stamp.mock.invocationCallOrder[0]);
    expect(loaded[1]).toMatchObject({crossOrigin:'anonymous'});expect(loaded[1].value).toMatch(/^https?:\/\/cdn.test/);
    expect(canvas.toBlob).toHaveBeenCalledWith(expect.any(Function),'image/png');
  });
  it('exports the post size with the same compositor and bottom-lip watermark', async()=>{
    imageMock(); const {canvas,ctx}=canvasMock();
    await composeShareImage('data:image/png;base64,RAW',metadata,card.CARD_SIZES.post);
    expect([canvas.width,canvas.height]).toEqual([1080,1350]);
    expect(vi.mocked(ctx.fillText).mock.calls.at(-1)).toEqual(['ai generated',1056,1334]);
  });
  it('uses placeholder after thumbnail load failure',async()=>{
    imageMock('garment');canvasMock();const draw=vi.spyOn(card,'drawShareCard');
    await composeShareImage('data:image/png;base64,RAW',{...metadata,productImage:'https://cdn.test/garment'});
    expect(draw.mock.calls[0][2]).toBeNull();
  });
  it('recomposes once with placeholder on taint; rejects a second failure',async()=>{
    imageMock();const {canvas}=canvasMock();const draw=vi.spyOn(card,'drawShareCard');
    canvas.toBlob.mockImplementationOnce(()=>{throw new DOMException('taint','SecurityError');});
    await composeShareImage('data:image/png;base64,RAW',{...metadata,productImage:'https://cdn.test/thumb'});
    expect(draw.mock.calls[0][2]).not.toBeNull();expect(draw.mock.calls[1][2]).toBeNull();
    canvas.toBlob.mockImplementation(()=>{throw new DOMException('taint','SecurityError');});
    await expect(composeShareImage('data:image/png;base64,RAW',metadata)).rejects.toThrow('taint');
    expect(canvas.toBlob).toHaveBeenCalledTimes(4);
  });
  it('fails closed for decoding, canvas and export failure',async()=>{
    imageMock('RAW');await expect(composeShareImage('data:image/png;base64,RAW',metadata)).rejects.toThrow('decoded');
    imageMock();const {canvas}=canvasMock(cb=>cb(null));
    await expect(composeShareImage('data:image/png;base64,RAW',metadata)).rejects.toThrow('export failed');
    canvas.getContext=()=>null as any;
    await expect(composeShareImage('data:image/png;base64,RAW',metadata)).rejects.toThrow('canvas unavailable');
  });
});
const file=new File(['CARD'],'card.png',{type:'image/png'});
it('never sends title, and tests file support before text',async()=>{
  const share=vi.fn().mockResolvedValue(undefined);const nav={share,canShare:vi.fn(()=>true)} as any;
  expect(detectSharePayloadMode(file,metadata,nav,'whatsapp_status')).toBe('file_text');
  await openShareSheet(file,metadata,nav);
  expect(share).toHaveBeenCalledWith({files:[file],text:buildShareMessage(metadata)});
  await openShareSheet(file,metadata,nav,'instagram_feed',true);
  expect(share).toHaveBeenLastCalledWith({files:[file]});
  expect(nav.canShare.mock.calls.every(([data]:[ShareData])=>!('title' in data))).toBe(true);
});
it('detects file-only, unsupported, partial implementations and unavailable document',async()=>{
  const share=vi.fn().mockResolvedValue(undefined);const nav={share,canShare:(data:ShareData)=>!data.text} as any;
  expect(detectSharePayloadMode(file,metadata,nav)).toBe('file_only');await openShareSheet(file,metadata,nav);expect(share).toHaveBeenCalledWith({files:[file]});
  for(const canShare of [()=>false,()=>{throw Error('partial');}]) {
    expect(detectSharePayloadMode(file,metadata,{share,canShare} as any)).toBe('none');
  }
  expect(detectShareCapability(file,{} as Navigator)).toBe('download_only');
  expect(detectShareCapability(file,{clipboard:{writeText:vi.fn()}} as any)).toBe('download_clipboard');
  expect(await openShareSheet(file,metadata,{} as Navigator)).toMatchObject({outcome:'fallback_ready'});
  vi.stubGlobal('document',undefined);expect(detectShareCapability(file,{} as Navigator)).toBe('unavailable');
});
it.each([['AbortError','cancelled'],['Error','failed']])('handles %s without throwing',async(name,outcome)=>{
  const nav={share:vi.fn().mockRejectedValue(new DOMException('no',name)),canShare:()=>true} as any;
  expect(await openShareSheet(file,metadata,nav)).toMatchObject({outcome});
});
it('copies, denies, and bounds pending clipboard at two seconds',async()=>{
  expect(await copyText('message',{} as Navigator)).toBe(false);
  expect(await copyText('message',{clipboard:{writeText:vi.fn().mockRejectedValue(Error())}} as any)).toBe(false);
  const writeText=vi.fn().mockResolvedValue(undefined);expect(await copyText('message',{clipboard:{writeText}} as any)).toBe(true);expect(writeText).toHaveBeenCalledWith('message');
  vi.useFakeTimers();const copying=copyText('x',{clipboard:{writeText:()=>new Promise(()=>{})}} as any);await vi.advanceTimersByTimeAsync(2000);expect(await copying).toBe(false);expect(vi.getTimerCount()).toBe(0);
});
it('downloads only the composed PNG, with deferred URL cleanup',()=>{
  vi.useFakeTimers();const createObjectURL=vi.fn(()=> 'blob:card'), revokeObjectURL=vi.fn();vi.stubGlobal('URL',{createObjectURL,revokeObjectURL});
  let anchor:HTMLAnchorElement;vi.spyOn(HTMLAnchorElement.prototype,'click').mockImplementation(function(this:HTMLAnchorElement){anchor=this;expect(this.isConnected).toBe(true);});
  const blob=new Blob(['CARD']);downloadShareImage(blob);expect(anchor!.download).toBe('aisthetix-try-on-story.png');expect(anchor!.isConnected).toBe(false);
  expect(shareFile(blob).name).toBe('aisthetix-try-on-story.png');vi.advanceTimersByTime(30000);expect(revokeObjectURL).toHaveBeenCalledWith('blob:card');
});
it.each([[{userAgentData:{mobile:false}},false],[{userAgentData:{mobile:true}},true],[{userAgent:'iPhone'},true],[{userAgent:'Macintosh',maxTouchPoints:5},true],[{},false]])('detects device %j',(nav,expected)=>expect(isMobileShareDevice(nav as Navigator)).toBe(expected));

it('builds transient messages without mutating metadata or browser storage', () => {
 const stored=vi.spyOn(Storage.prototype,'setItem');
 const frozen=Object.freeze({...metadata});
 for(const channel of ['instagram_stories','instagram_feed','whatsapp_status','whatsapp_message'] as ShareChannel[]) shareClipboardText(frozen,channel);
 expect(stored).not.toHaveBeenCalled();expect(frozen.productUrl).toBe('https://shop.test/p?variant=7');
});
