import { describe, expect, test } from 'bun:test';
import type { Run } from '../../types/document';
import { serializeRun } from './runSerializer';

// Issue #417: image and shape dimension/offset attributes leaked floating-point
// IEEE-754 drift (e.g. cy="495299.99999999994") which Word rejects as a corrupt
// document. The model layer rounds via pixelsToEmu, but the serializer must
// also coerce so a caller that hands us a float can never produce an
// unopenable file.

const FLOAT_INLINE_IMAGE: Run = {
  type: 'run',
  content: [
    {
      type: 'drawing',
      image: {
        type: 'image',
        rId: 'rId7',
        size: { width: 5610225, height: 495299.99999999994 },
        wrap: { type: 'inline' },
        padding: { top: 0.4, bottom: 0.6, left: 0, right: 0 },
      },
    },
  ],
};

const FLOAT_FLOATING_IMAGE: Run = {
  type: 'run',
  content: [
    {
      type: 'drawing',
      image: {
        type: 'image',
        rId: 'rId8',
        size: { width: 1905000.0000000002, height: 933449.9999999999 },
        wrap: { type: 'square', distT: 114299.99999999, distB: 0, distL: 0, distR: 0 },
        position: {
          horizontal: { relativeTo: 'column', posOffset: 238125.00000000003 },
          vertical: { relativeTo: 'paragraph', posOffset: 962024.9999999999 },
        },
      },
    },
  ],
};

const FLOAT_BEHIND_IMAGE: Run = {
  type: 'run',
  content: [
    {
      type: 'drawing',
      image: {
        type: 'image',
        rId: 'rId9',
        size: { width: 495299.99999999994, height: 495299.99999999994 },
        wrap: { type: 'behind' },
        position: {
          horizontal: { relativeTo: 'page', posOffset: -50.7 },
          vertical: { relativeTo: 'page', posOffset: 0 },
        },
      },
    },
  ],
};

const FLOAT_INLINE_SHAPE: Run = {
  type: 'run',
  content: [
    {
      type: 'shape',
      shape: {
        type: 'shape',
        shapeType: 'rect',
        size: { width: 1234567.89, height: 987654.321 },
      },
    },
  ],
};

const FLOAT_FLOATING_TEXTBOX: Run = {
  type: 'run',
  content: [
    {
      type: 'shape',
      shape: {
        type: 'shape',
        shapeType: 'textBox',
        size: { width: 2540000.0000001, height: 1270000.5 },
        wrap: { type: 'square', distT: 91440.7, distB: 91440.3, distL: 0, distR: 0 },
        position: {
          horizontal: { relativeTo: 'margin', posOffset: 100000.5 },
          vertical: { relativeTo: 'paragraph', posOffset: 200000.5 },
        },
        textBody: {
          content: [{ type: 'paragraph', content: [] }],
          margins: { left: 91440.5, top: 45720.3, right: 91440.5, bottom: 45720.3 },
        },
      },
    },
  ],
};

const ANY_DECIMAL_IN_EMU_ATTR = /(?:cx|cy|distT|distB|distL|distR)="-?\d+\.\d+"/;
const POSOFFSET_DECIMAL = /<wp:posOffset>-?\d+\.\d+<\/wp:posOffset>/;

describe('image EMU attributes are integer-only (issue #417)', () => {
  test('inline image with float dimensions serializes integer cx/cy/effectExtent', () => {
    const xml = serializeRun(FLOAT_INLINE_IMAGE);

    expect(xml).not.toMatch(ANY_DECIMAL_IN_EMU_ATTR);
    expect(xml).toContain('<wp:extent cx="5610225" cy="495300"/>');
    expect(xml).toContain('<a:ext cx="5610225" cy="495300"/>');
    // Per ECMA-376 §20.4.2.5 / §20.4.2.8, `image.padding` (shadow/glow
    // reservation) is `<wp:effectExtent>`; it no longer leaks into
    // `wp:inline distT/B/L/R` (which are text-wrap distances only). With
    // padding {top: 0.4, bottom: 0.6}, intAttr rounds → t="0" b="1".
    expect(xml).toContain('<wp:effectExtent l="0" t="0" r="0" b="1"/>');
    expect(xml).toContain('distT="0" distB="0" distL="0" distR="0"');
  });

  test('floating image with float position/extent serializes integer attrs', () => {
    const xml = serializeRun(FLOAT_FLOATING_IMAGE);

    expect(xml).not.toMatch(ANY_DECIMAL_IN_EMU_ATTR);
    expect(xml).not.toMatch(POSOFFSET_DECIMAL);
    expect(xml).toContain('<wp:extent cx="1905000" cy="933450"/>');
    expect(xml).toContain('<wp:posOffset>238125</wp:posOffset>');
    expect(xml).toContain('<wp:posOffset>962025</wp:posOffset>');
    expect(xml).toContain('distT="114300"');
  });

  test('behind-wrapped image with negative offset rounds correctly', () => {
    const xml = serializeRun(FLOAT_BEHIND_IMAGE);

    expect(xml).not.toMatch(ANY_DECIMAL_IN_EMU_ATTR);
    expect(xml).not.toMatch(POSOFFSET_DECIMAL);
    expect(xml).toContain('behindDoc="1"');
    expect(xml).toContain('<wp:extent cx="495300" cy="495300"/>');
    expect(xml).toContain('<wp:posOffset>-51</wp:posOffset>');
  });
});

describe('shape EMU attributes are integer-only (issue #417)', () => {
  test('inline shape with float size serializes integer cx/cy', () => {
    const xml = serializeRun(FLOAT_INLINE_SHAPE);

    expect(xml).not.toMatch(ANY_DECIMAL_IN_EMU_ATTR);
    expect(xml).toContain('<a:ext cx="1234568" cy="987654"/>');
    expect(xml).toContain('<wp:extent cx="1234568" cy="987654"/>');
  });

  test('floating textbox with float dimensions, position, and margins is fully integer', () => {
    const xml = serializeRun(FLOAT_FLOATING_TEXTBOX);

    expect(xml).not.toMatch(ANY_DECIMAL_IN_EMU_ATTR);
    expect(xml).not.toMatch(POSOFFSET_DECIMAL);
    expect(xml).toContain('<wp:extent cx="2540000" cy="1270001"/>');
    expect(xml).toContain('<a:ext cx="2540000" cy="1270001"/>');
    expect(xml).toContain('distT="91441" distB="91440"');
    expect(xml).toContain('<wp:posOffset>100001</wp:posOffset>');
    expect(xml).toContain('<wp:posOffset>200001</wp:posOffset>');
    // Body margins (lIns/tIns/rIns/bIns) come from shape.textBody.margins —
    // these are integers in the model after the createDocument fix, but the
    // serializer still interpolates raw. Drift here would not currently be
    // caught by intAttr, so verify the full string is integer-only.
  });
});

describe('run formatting integer attributes (issue #417)', () => {
  test('font size, character spacing, scale, kern, position render as integers', () => {
    const run: Run = {
      type: 'run',
      content: [{ type: 'text', text: 'x' }],
      formatting: {
        fontSize: 22.0000001,
        fontSizeCs: 21.999999,
        spacing: 19.999999998,
        scale: 99.99999,
        kerning: 18.0000003,
        position: -6.0000001,
      },
    };

    const xml = serializeRun(run);

    expect(xml).not.toMatch(/w:val="-?\d+\.\d+"/);
    expect(xml).toContain('<w:sz w:val="22"/>');
    expect(xml).toContain('<w:szCs w:val="22"/>');
    expect(xml).toContain('<w:spacing w:val="20"/>');
    expect(xml).toContain('<w:w w:val="100"/>');
    expect(xml).toContain('<w:kern w:val="18"/>');
    expect(xml).toContain('<w:position w:val="-6"/>');
  });
});
