import { describe, it, expect } from 'vitest';
import PptxGenJS from 'pptxgenjs';
import * as fs from 'fs';
import * as path from 'path';
import { tmpdir } from 'os';
import { renderSlide, toBulletItems } from './layouts.js';
import { resolveTheme } from './themes.js';

function newDeck() {
  const p = new PptxGenJS();
  p.layout = 'LAYOUT_WIDE';   // 13.33 x 7.5 inch
  return p;
}

function makeWsWithImage(): { ws: string; rel: string } {
  const ws = fs.mkdtempSync(path.join(tmpdir(), 'slide-layout-img-'));
  fs.mkdirSync(path.join(ws, 'input'), { recursive: true });
  const png = Buffer.from(
    '89504E470D0A1A0A0000000D49484452000000010000000108060000001F15C4890000000A4944415478DA63000100000500010D0A2DB40000000049454E44AE426082',
    'hex',
  );
  fs.writeFileSync(path.join(ws, 'input/sample.png'), png);
  return { ws, rel: 'input/sample.png' };
}

describe('layouts: title / section / bullets / closing', () => {
  const theme = resolveTheme('minimal-mono', {});

  it('renders title without throwing', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() =>
      renderSlide(s, 'title', { title: 'Hello', subtitle: 'World', author: 'me', date: '2026-05-22' }, theme, { index: 0, total: 1 }),
    ).not.toThrow();
  });

  it('renders section without throwing', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() =>
      renderSlide(s, 'section', { number: '01', title: '背景' }, theme, { index: 1, total: 5 }),
    ).not.toThrow();
  });

  it('renders bullets without throwing', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() =>
      renderSlide(s, 'bullets', { title: 'Points', bullets: ['a', 'b', 'c'], footnote: 'src' }, theme, { index: 2, total: 5 }),
    ).not.toThrow();
  });

  it('renders object bullets without throwing', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() =>
      renderSlide(s, 'bullets', {
        title: 'Mixed',
        bullets: ['plain', { text: 'parent', bullets: [{ text: 'child', bold: true }, 'child2'] }],
      }, theme, { index: 2, total: 5 }),
    ).not.toThrow();
  });

  it('renders closing without throwing', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() =>
      renderSlide(s, 'closing', { message: 'Thank you', contact: 'me@example.com' }, theme, { index: 4, total: 5 }),
    ).not.toThrow();
  });
});

describe('toBulletItems', () => {
  it('passes plain strings through with the bullet glyph', () => {
    const items = toBulletItems(['a', 'b']);
    expect(items.map(i => i.text)).toEqual(['a', 'b']);
    expect(items[0].options.bullet.code).toBe('25CF');
  });

  it('extracts the text field from object bullets instead of "[object Object]"', () => {
    const items = toBulletItems([{ text: 'hello' }, { label: 'world' }, { title: 'z' }]);
    expect(items.map(i => i.text)).toEqual(['hello', 'world', 'z']);
    expect(items.some(i => i.text.includes('[object Object]'))).toBe(false);
  });

  it('flattens nested sub-bullets at a deeper indentLevel', () => {
    const items = toBulletItems([{ text: 'parent', bullets: ['c1', 'c2'] }]);
    expect(items.map(i => i.text)).toEqual(['parent', 'c1', 'c2']);
    expect(items[0].options.indentLevel).toBeUndefined();
    expect(items[1].options.indentLevel).toBe(1);
    expect(items[2].options.indentLevel).toBe(1);
  });

  it('honors bold on object bullets', () => {
    const items = toBulletItems([{ text: 'strong', bold: true }, { text: 'normal' }]);
    expect(items[0].options.bold).toBe(true);
    expect(items[1].options.bold).toBeUndefined();
  });

  it('renders a parentless sub-bullets object without an empty line', () => {
    const items = toBulletItems([{ bullets: ['only-child'] }]);
    expect(items.map(i => i.text)).toEqual(['only-child']);
    expect(items[0].options.indentLevel).toBe(1);
  });

  it('falls back to JSON (never "[object Object]") for a text-less junk object', () => {
    const items = toBulletItems([{ foo: 1 }]);
    expect(items).toHaveLength(1);
    expect(items[0].text).not.toContain('[object Object]');
    expect(items[0].text).toContain('foo');
  });
});

describe('layouts: two-column / image-right / image-left / image-full', () => {
  const theme = resolveTheme('corporate-blue', {});

  it('renders two-column', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'two-column', {
      title: 'A vs B',
      left:  { heading: '現状', bullets: ['x', 'y'] },
      right: { heading: '改善', bullets: ['p', 'q'] },
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });

  it('renders image-right', () => {
    const { ws, rel } = makeWsWithImage();
    try {
      const p = newDeck();
      const s = p.addSlide();
      expect(() => renderSlide(s, 'image-right', {
        title: 'X', body: 'body text',
        image: { path: rel },
      }, theme, { index: 0, total: 1 }, { workspacePath: ws })).not.toThrow();
    } finally { fs.rmSync(ws, { recursive: true, force: true }); }
  });

  it('renders image-left', () => {
    const { ws, rel } = makeWsWithImage();
    try {
      const p = newDeck();
      const s = p.addSlide();
      expect(() => renderSlide(s, 'image-left', {
        title: 'X', body: 'body text',
        image: { path: rel },
      }, theme, { index: 0, total: 1 }, { workspacePath: ws })).not.toThrow();
    } finally { fs.rmSync(ws, { recursive: true, force: true }); }
  });

  it('renders image-full', () => {
    const { ws, rel } = makeWsWithImage();
    try {
      const p = newDeck();
      const s = p.addSlide();
      expect(() => renderSlide(s, 'image-full', {
        image: { path: rel }, caption: 'caption',
      }, theme, { index: 0, total: 1 }, { workspacePath: ws })).not.toThrow();
    } finally { fs.rmSync(ws, { recursive: true, force: true }); }
  });
});

describe('layouts: table / chart', () => {
  const theme = resolveTheme('academic', {});

  it('renders table with default col widths', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'table', {
      title: 'Comparison',
      headers: ['Item', 'A', 'B'],
      rows: [['Price', '100', '200'], ['Speed', 'Fast', 'Slow']],
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });

  it('renders table with explicit ratios', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'table', {
      title: 'X',
      headers: ['a', 'b', 'c'],
      rows: [['1', '2', '3']],
      col_widths: [0.2, 0.3, 0.5],
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });

  it('renders bar chart', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'chart', {
      title: 'Q1', chart_type: 'bar',
      data: { categories: ['Jan', 'Feb', 'Mar'], series: [{ name: 'Sales', values: [10, 20, 30] }] },
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });

  it('renders pie chart', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'chart', {
      title: 'Share', chart_type: 'pie',
      data: { categories: ['A', 'B'], series: [{ name: 'Share', values: [60, 40] }] },
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });
});

describe('layouts: quote / custom', () => {
  const theme = resolveTheme('warm-paper', {});

  it('renders quote with attribution', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'quote', {
      quote: 'Stay hungry, stay foolish.',
      attribution: 'Steve Jobs',
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });

  it('renders custom text element', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'custom', {
      elements: [{ type: 'text', text: 'Hi', x: 1, y: 1, w: 4, h: 1, options: { font_size: 24, bold: true } }],
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });

  it('renders custom shape element', () => {
    const p = newDeck();
    const s = p.addSlide();
    expect(() => renderSlide(s, 'custom', {
      elements: [{ type: 'shape', shape: 'rect', x: 1, y: 1, w: 4, h: 2, options: { fill: '#FF0000' } }],
    }, theme, { index: 0, total: 1 })).not.toThrow();
  });
});
