// DOM-based Timeline Generator
// Uses proper DOM manipulation instead of string templates
// SVG remains 100% valid and Inkscape-editable

window.timelineGeneratorDOM = {
  generate(items, cfg, templateString) {
    if (!templateString) {
      throw new Error('Template is required. Please provide an SVG file with prototype elements.');
    }

    // Parse SVG as DOM
    const parser = new DOMParser();
    const doc = parser.parseFromString(templateString, 'image/svg+xml');

    // Check for parse errors
    const parserError = doc.querySelector('parsererror');
    if (parserError) {
      throw new Error('Failed to parse SVG template: ' + parserError.textContent);
    }

    const svg = doc.documentElement;

    // Validate required prototypes exist
    this.validatePrototypes(svg);

    // Calculate layout
    const layout = this.calculateLayout(items, cfg);

    // Generate timeline using DOM cloning
    this.generateFromPrototypes(svg, items, cfg, layout);

    // Set SVG dimensions
    svg.setAttribute('width', layout.width);
    svg.setAttribute('height', layout.height);
    svg.setAttribute('viewBox', `0 0 ${layout.width} ${layout.height}`);

    // Serialize back to string
    const serializer = new XMLSerializer();
    return serializer.serializeToString(svg);
  },

  validatePrototypes(svg) {
    const requiredIds = ['month-proto', 'lane-proto', 'item-proto'];
    const missing = [];

    for (const id of requiredIds) {
      if (!svg.querySelector(`#${id}`)) {
        missing.push(id);
      }
    }

    if (missing.length > 0) {
      throw new Error(
        `Template is missing required prototype elements: ${missing.join(', ')}. ` +
        `Please create prototype groups with these IDs in your SVG.`
      );
    }

    // Check for target containers (create if missing)
    const containers = ['months-container', 'lanes-container', 'items-container'];
    for (const id of containers) {
      if (!svg.querySelector(`#${id}`)) {
        console.warn(`Container #${id} not found, will create it`);
      }
    }
  },

  calculateLayout(items, cfg) {
    const monthsRange = (cfg.settings && cfg.settings.timelineMonths) || 18;

    // Determine time window
    const sorted = [...items].sort((a, b) => a.due - b.due);
    const minDue = sorted[0].due;
    const start = new Date(minDue.getFullYear(), minDue.getMonth(), 1);
    const end = new Date(start);
    end.setMonth(end.getMonth() + monthsRange);

    // Build months array
    const months = [];
    const cursor = new Date(start);
    while (cursor <= end) {
      months.push(new Date(cursor));
      cursor.setMonth(cursor.getMonth() + 1);
    }

    // Group items by lane
    const laneMap = new Map();
    for (const it of items) {
      const laneName = it.lane || "Ohne Epic";
      if (!laneMap.has(laneName)) laneMap.set(laneName, []);
      laneMap.get(laneName).push(it);
    }

    const laneNames = Array.from(laneMap.keys()).sort((a, b) =>
      a.localeCompare(b, "de")
    );

    // Layout constants (can be overridden in cfg.settings)
    const left = (cfg.settings && cfg.settings.marginLeft) || 220;
    const top = (cfg.settings && cfg.settings.marginTop) || 140;
    const monthWidth = (cfg.settings && cfg.settings.monthWidth) || 120;
    const laneHeight = (cfg.settings && cfg.settings.laneHeight) || 80;
    const laneGap = (cfg.settings && cfg.settings.laneGap) || 16;

    const height = top + laneNames.length * (laneHeight + laneGap) + 80;
    const width = left + months.length * monthWidth + 100;

    return {
      months, start, laneMap, laneNames,
      left, top, monthWidth, laneHeight, laneGap,
      width, height
    };
  },

  generateFromPrototypes(svg, items, cfg, layout) {
    const { months, start, laneMap, laneNames, left, top, monthWidth, laneHeight, laneGap } = layout;

    // Find prototypes
    const monthProto = svg.querySelector('#month-proto');
    const laneProto = svg.querySelector('#lane-proto');
    const itemProto = svg.querySelector('#item-proto');

    // Find or create containers
    let monthsContainer = svg.querySelector('#months-container');
    let lanesContainer = svg.querySelector('#lanes-container');
    let itemsContainer = svg.querySelector('#items-container');

    if (!monthsContainer) {
      monthsContainer = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'g');
      monthsContainer.setAttribute('id', 'months-container');
      svg.appendChild(monthsContainer);
    }

    if (!lanesContainer) {
      lanesContainer = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'g');
      lanesContainer.setAttribute('id', 'lanes-container');
      svg.appendChild(lanesContainer);
    }

    if (!itemsContainer) {
      itemsContainer = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'g');
      itemsContainer.setAttribute('id', 'items-container');
      svg.appendChild(itemsContainer);
    }

    // Generate months
    const monthLabelY = 90;
    const gridTop = top - 20;
    const gridBottom = top + laneNames.length * (laneHeight + laneGap) + 40;

    months.forEach((m, i) => {
      const x = left + i * monthWidth;
      const label = m.toLocaleString("de-DE", { month: "short", year: "2-digit" });

      const clone = monthProto.cloneNode(true);
      clone.removeAttribute('id'); // Remove id to avoid duplicates
      clone.setAttribute('transform', `translate(${x}, 0)`);

      // Update text content
      const labelElement = clone.querySelector('[id*="label"], text');
      if (labelElement) {
        labelElement.textContent = label;
      }

      // Update grid line positions (if present)
      const gridLine = clone.querySelector('line');
      if (gridLine) {
        gridLine.setAttribute('y1', gridTop);
        gridLine.setAttribute('y2', gridBottom);
      }

      monthsContainer.appendChild(clone);
    });

    // Helper function
    function monthIndexForDate(d) {
      return (d.getFullYear() - start.getFullYear()) * 12
           + (d.getMonth() - start.getMonth());
    }

    // Generate lanes
    laneNames.forEach((laneName, laneIdx) => {
      const laneY = top + laneIdx * (laneHeight + laneGap);

      const clone = laneProto.cloneNode(true);
      clone.removeAttribute('id');
      clone.setAttribute('transform', `translate(0, ${laneY})`);

      // Update lane label
      const labelElement = clone.querySelector('[id*="label"], text');
      if (labelElement) {
        labelElement.textContent = laneName;
      }

      // Update lane background width (if rect present)
      const bgRect = clone.querySelector('rect');
      if (bgRect) {
        const laneWidth = left + months.length * monthWidth;
        bgRect.setAttribute('width', laneWidth);
        bgRect.setAttribute('height', laneHeight);
      }

      lanesContainer.appendChild(clone);

      // Generate items for this lane
      const laneItems = laneMap.get(laneName).sort((a, b) => a.due - b.due);
      laneItems.forEach((it, idx) => {
        const mi = monthIndexForDate(it.due);
        const clampedMi = Math.max(0, Math.min(months.length - 1, mi));
        const cx = left + clampedMi * monthWidth + monthWidth * 0.5;
        const cy = laneY + 10 + idx * 18;

        const itemClone = itemProto.cloneNode(true);
        itemClone.removeAttribute('id');
        itemClone.setAttribute('transform', `translate(${cx}, ${cy})`);

        // Update text elements based on field mapping
        const fieldMapping = cfg.fieldMapping || {
          id: 'ID',
          title: 'Title',
          lane: 'Lane',
          due: 'Due'
        };

        // Map data to text elements
        for (const [fieldName, csvColumn] of Object.entries(fieldMapping)) {
          if (fieldName === 'due') continue; // Skip due, used for positioning

          const value = it[fieldName] || '';
          // Try to find text element with matching id
          const textElement = itemClone.querySelector(`#item-${fieldName}, [id*="${fieldName}"]`);
          if (textElement) {
            textElement.textContent = value;
            textElement.removeAttribute('id'); // Avoid duplicate IDs
          }
        }

        // Also check for generic text element if no specific mapping found
        if (!itemClone.querySelector('text[id]')) {
          const anyText = itemClone.querySelector('text');
          if (anyText) {
            anyText.textContent = it.title || it.id || '';
          }
        }

        itemsContainer.appendChild(itemClone);
      });
    });

    // Hide or remove prototypes
    monthProto.style.display = 'none';
    laneProto.style.display = 'none';
    itemProto.style.display = 'none';
  },

  escapeXml(str) {
    return String(str)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#39;");
  }
};
