import { describe, it, expect, beforeEach } from 'vitest'
import { createSampleItems, createSampleProject, createSampleTemplate, createMalformedTemplate } from './testHelpers.js'

// Import generator by loading it as text and evaluating
const fs = await import('fs/promises')
const generatorCode = await fs.readFile('./generator.js', 'utf-8')

describe('Timeline Generator', () => {
  let timelineGenerator

  beforeEach(() => {
    // Reset global window object
    global.window = global

    // Execute generator code
    eval(generatorCode)
    timelineGenerator = global.window.timelineGenerator
  })

  describe('escapeXml', () => {
    it('should escape XML special characters', () => {
      const input = '<test & "quotes" & \'apostrophes\'>'
      const expected = '&lt;test &amp; &quot;quotes&quot; &amp; &#39;apostrophes&#39;&gt;'
      expect(timelineGenerator.escapeXml(input)).toBe(expected)
    })

    it('should handle empty and null values', () => {
      expect(timelineGenerator.escapeXml('')).toBe('')
      expect(timelineGenerator.escapeXml(null)).toBe('null')
      expect(timelineGenerator.escapeXml(undefined)).toBe('undefined')
    })

    it('should convert non-strings to strings', () => {
      expect(timelineGenerator.escapeXml(123)).toBe('123')
      expect(timelineGenerator.escapeXml(true)).toBe('true')
    })
  })

  describe('Template validation', () => {
    let items, config

    beforeEach(() => {
      items = createSampleItems()
      config = createSampleProject()
    })

    it('should throw error when template is not provided', () => {
      expect(() => {
        timelineGenerator.generate(items, config, null)
      }).toThrow('Template is required')
    })

    it('should throw error when template is missing month-template', () => {
      const malformedTemplate = createMalformedTemplate('month-template')

      expect(() => {
        timelineGenerator.generate(items, config, malformedTemplate)
      }).toThrow('Template is missing required elements: month-template')
    })

    it('should throw error when template is missing lane-template', () => {
      const malformedTemplate = createMalformedTemplate('lane-template')

      expect(() => {
        timelineGenerator.generate(items, config, malformedTemplate)
      }).toThrow('Template is missing required elements: lane-template')
    })

    it('should throw error when template is missing item-template', () => {
      const malformedTemplate = createMalformedTemplate('item-template')

      expect(() => {
        timelineGenerator.generate(items, config, malformedTemplate)
      }).toThrow('Template is missing required elements: item-template')
    })

    it('should validate template with proper error message', () => {
      const emptyTemplate = '<svg></svg>'

      expect(() => {
        timelineGenerator.generate(items, config, emptyTemplate)
      }).toThrow('Please use a template-v2.svg file with proper template elements')
    })
  })

  describe('Template extraction', () => {
    it('should extract template element by id', () => {
      const svg = `
        <svg>
          <g id="month-template">
            <line x1="{{X}}" y1="0"/>
            <text>{{LABEL}}</text>
          </g>
        </svg>
      `
      const result = timelineGenerator.extractTemplate(svg, 'month-template')
      expect(result).toContain('id="month-template"')
      expect(result).toContain('{{X}}')
      expect(result).toContain('{{LABEL}}')
    })

    it('should throw error when template not found', () => {
      const svg = '<svg><g id="other"></g></svg>'

      expect(() => {
        timelineGenerator.extractTemplate(svg, 'month-template')
      }).toThrow('Failed to extract template element: month-template')
    })

    it('should extract nested elements within template', () => {
      const svg = `
        <svg>
          <g id="lane-template">
            <rect fill="#FFF"/>
            <text>Label</text>
          </g>
        </svg>
      `
      const result = timelineGenerator.extractTemplate(svg, 'lane-template')
      expect(result).toContain('<rect fill="#FFF"/>')
      expect(result).toContain('<text>Label</text>')
    })
  })

  describe('Placeholder replacement', () => {
    it('should replace all placeholders with values', () => {
      const template = '<text x="{{X}}" y="{{Y}}">{{LABEL}}</text>'
      const values = { X: 100, Y: 200, LABEL: 'Test' }
      const result = timelineGenerator.replacePlaceholders(template, values)
      expect(result).toBe('<text x="100" y="200">Test</text>')
    })

    it('should handle multiple occurrences of same placeholder', () => {
      const template = '<g><rect x="{{X}}"/><circle cx="{{X}}"/></g>'
      const values = { X: 50 }
      const result = timelineGenerator.replacePlaceholders(template, values)
      expect(result).toBe('<g><rect x="50"/><circle cx="50"/></g>')
    })

    it('should leave unmatched placeholders unchanged', () => {
      const template = '<text>{{LABEL}} {{OTHER}}</text>'
      const values = { LABEL: 'Test' }
      const result = timelineGenerator.replacePlaceholders(template, values)
      expect(result).toContain('Test')
      expect(result).toContain('{{OTHER}}')
    })

    it('should handle numeric and boolean values', () => {
      const template = '<rect x="{{X}}" visible="{{VISIBLE}}"/>'
      const values = { X: 42, VISIBLE: true }
      const result = timelineGenerator.replacePlaceholders(template, values)
      expect(result).toBe('<rect x="42" visible="true"/>')
    })
  })

  describe('Template-based SVG generation', () => {
    let items, config

    beforeEach(() => {
      items = createSampleItems()
      config = createSampleProject()
    })

    it('should generate SVG with template-v2 format', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      expect(result).toContain('<svg xmlns="http://www.w3.org/2000/svg"')
      expect(result).toContain('width=')
      expect(result).toContain('height=')
      expect(result).toContain('<rect width="100%" height="100%" fill="#FFFFFF"/>')
      expect(result).not.toContain('{{MONTHS}}')
      expect(result).not.toContain('{{LANES}}')
    })

    it('should not contain template placeholders in output', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      expect(result).not.toContain('{{MONTH_X}}')
      expect(result).not.toContain('{{LANE_Y}}')
      expect(result).not.toContain('{{ITEM_X}}')
      expect(result).not.toContain('{{ITEM_TITLE}}')
    })

    it('should contain task data in generated SVG', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      expect(result).toContain('T-1')
      expect(result).toContain('First Task')
      expect(result).toContain('T-2')
      expect(result).toContain('Second Task')
      expect(result).toContain('T-3')
      expect(result).toContain('Third Task')
    })

    it('should contain lane names in generated SVG', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      expect(result).toContain('Development')
      expect(result).toContain('Testing')
    })

    it('should escape special characters in task data', () => {
      const itemsWithSpecialChars = [{
        id: 'T&1',
        title: 'Task with <special> & "characters"',
        lane: 'Development',
        due: new Date('2025-01-01')
      }]

      const template = createSampleTemplate()
      const result = timelineGenerator.generate(itemsWithSpecialChars, config, template)

      expect(result).toContain('T&amp;1')
      expect(result).toContain('&lt;special&gt; &amp; &quot;characters&quot;')
    })

    it('should handle items without lanes', () => {
      const itemsNoLane = [
        { id: 'T-1', title: 'No Lane Task', lane: null, due: new Date('2025-01-01') }
      ]

      const template = createSampleTemplate()
      const result = timelineGenerator.generate(itemsNoLane, config, template)
      expect(result).toContain('Ohne Epic')
    })

    it('should respect timelineMonths setting', () => {
      const template = createSampleTemplate()
      const shortConfig = { ...config, settings: { timelineMonths: 6 } }
      const longConfig = { ...config, settings: { timelineMonths: 24 } }

      const shortResult = timelineGenerator.generate(items, shortConfig, template)
      const longResult = timelineGenerator.generate(items, longConfig, template)

      // Longer timeline should produce larger SVG
      const shortWidth = shortResult.match(/width="(\d+)"/)?.[1] || 0
      const longWidth = longResult.match(/width="(\d+)"/)?.[1] || 0
      expect(parseInt(longWidth)).toBeGreaterThan(parseInt(shortWidth))
    })

    it('should not contain template elements with display:none in output', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      expect(result).not.toContain('id="month-template"')
      expect(result).not.toContain('id="lane-template"')
      expect(result).not.toContain('id="item-template"')
      expect(result).not.toContain('style="display:none"')
    })

    it('should preserve template styling in generated output', async () => {
      const templateV2 = await fs.readFile('./example/template-v2.svg', 'utf-8')
      const result = timelineGenerator.generate(items, config, templateV2)

      // Should preserve gradient and filter definitions
      expect(result).toContain('monthHeaderGrad')
      expect(result).toContain('textShadow')
      expect(result).toContain('bgGrid')
    })

    it('should set viewBox dimensions correctly', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      const widthMatch = result.match(/width="(\d+)"/)
      const heightMatch = result.match(/height="(\d+)"/)
      const viewBoxMatch = result.match(/viewBox="0 0 (\d+) (\d+)"/)

      expect(widthMatch).toBeTruthy()
      expect(heightMatch).toBeTruthy()
      expect(viewBoxMatch).toBeTruthy()

      // viewBox should match width and height
      expect(viewBoxMatch[1]).toBe(widthMatch[1])
      expect(viewBoxMatch[2]).toBe(heightMatch[1])
    })

    it('should support custom placeholder mapping', () => {
      const customConfig = {
        ...config,
        placeholderMapping: {
          id: 'TASK_ID',      // Map item.id to {{TASK_ID}} instead of {{ITEM_ID}}
          title: 'TASK_NAME'  // Map item.title to {{TASK_NAME}} instead of {{ITEM_TITLE}}
        }
      }

      // Create template with custom placeholders
      const customTemplate = `<svg xmlns="http://www.w3.org/2000/svg">
        <defs>
          <g id="month-template" style="display:none">
            <text x="{{MONTH_TEXT_X}}" y="{{MONTH_LABEL_Y}}">{{MONTH_LABEL}}</text>
          </g>
          <g id="lane-template" style="display:none">
            <text>{{LANE_NAME}}</text>
          </g>
          <g id="item-template" style="display:none">
            <text x="{{TEXT_X}}" y="{{TEXT_Y}}">{{TASK_ID}}: {{TASK_NAME}}</text>
          </g>
        </defs>
        {{MONTHS}}
        {{LANES}}
      </svg>`

      const result = timelineGenerator.generate(items, customConfig, customTemplate)

      // Should use custom placeholder names
      expect(result).toContain('T-1: First Task')  // TASK_ID: TASK_NAME
      expect(result).toContain('T-2: Second Task')

      // Should not contain default placeholder syntax
      expect(result).not.toContain('{{ITEM_ID}}')
      expect(result).not.toContain('{{ITEM_TITLE}}')
    })

    it('should use default convention when placeholderMapping is not provided', () => {
      const template = createSampleTemplate()
      const result = timelineGenerator.generate(items, config, template)

      // Should use default ITEM_* convention
      expect(result).toContain('T-1 First Task')  // Default template has space, not colon
      expect(result).not.toContain('{{ITEM_ID}}')
      expect(result).not.toContain('{{ITEM_TITLE}}')
    })
  })
})
