/**
 * Section-Level Diff & Merge for VL files
 *
 * VL files have a deterministic section structure separated by "# SectionName" markers.
 * This enables high-fidelity merges: only replace changed sections, preserve user edits.
 *
 * API:
 *   parseSections(content)                              → Map<name, content>
 *   sectionDiff(oldContent, newContent)                 → { added, removed, modified, unchanged, details }
 *   mergeSections(base, incoming, strategy, toReplace?) → merged string
 */

/**
 * Parse a VL file into sections.
 * Returns Map<sectionName, fullSectionContent> where the content includes the "# Header" line.
 * Content before the first section is stored under the "_header" key.
 */
export function parseSections(content) {
  const sections = new Map();
  const lines = content.split('\n');

  let currentName = '_header';
  let currentLines = [];

  for (const line of lines) {
    const m = line.match(/^#\s+(.+)$/);
    if (m) {
      // Flush previous section (trim trailing blank lines, but keep at least one separator newline)
      sections.set(currentName, currentLines.join('\n'));
      currentName = m[1].trim();
      currentLines = [line];  // Include the "# SectionName" header line
    } else {
      currentLines.push(line);
    }
  }
  // Flush last section
  sections.set(currentName, currentLines.join('\n'));

  return sections;
}

/**
 * Diff two VL file contents at the section level.
 *
 * Returns:
 *   added:     section names present in newContent but not oldContent
 *   removed:   section names present in oldContent but not newContent
 *   modified:  section names present in both with different content
 *   unchanged: section names present in both with identical content
 *   details:   Map<name, { old: string, new: string }> for modified sections
 */
export function sectionDiff(oldContent, newContent) {
  const oldSections = parseSections(oldContent);
  const newSections = parseSections(newContent);

  const added    = [];
  const removed  = [];
  const modified = [];
  const unchanged = [];
  const details  = new Map();

  for (const [name, newVal] of newSections) {
    if (!oldSections.has(name)) {
      added.push(name);
    } else if (oldSections.get(name) !== newVal) {
      modified.push(name);
      details.set(name, { old: oldSections.get(name), new: newVal });
    } else {
      unchanged.push(name);
    }
  }

  for (const name of oldSections.keys()) {
    if (!newSections.has(name)) removed.push(name);
  }

  return { added, removed, modified, unchanged, details };
}

/**
 * Merge two VL file contents at the section level.
 *
 * strategy:
 *   "prefer_new"  — new content wins for all shared sections; base sections not in new are kept;
 *                   sections removed from new are dropped from base.
 *   "prefer_base" — base wins for shared sections; only sections absent from base are added from new.
 *   "selective"   — only replace sections listed in sectionsToReplace; add new sections not in base.
 *
 * The section order follows the base file, with any new-only sections appended at the end.
 */
export function mergeSections(baseContent, incomingContent, strategy = 'prefer_new', sectionsToReplace = []) {
  const baseSections     = parseSections(baseContent);
  const incomingSections = parseSections(incomingContent);

  const merged = new Map(baseSections);

  if (strategy === 'prefer_new') {
    // Update all shared sections + add new sections
    for (const [name, content] of incomingSections) {
      merged.set(name, content);
    }
    // Drop sections explicitly removed in incoming (not present in incoming)
    for (const name of baseSections.keys()) {
      if (name !== '_header' && !incomingSections.has(name)) {
        merged.delete(name);
      }
    }
  } else if (strategy === 'prefer_base') {
    // Only add sections that don't exist in base
    for (const [name, content] of incomingSections) {
      if (!baseSections.has(name)) merged.set(name, content);
    }
  } else if (strategy === 'selective') {
    const toReplace = new Set(sectionsToReplace);
    for (const [name, content] of incomingSections) {
      if (toReplace.has(name)) {
        merged.set(name, content);
      } else if (!baseSections.has(name)) {
        merged.set(name, content);  // Add new sections not in base
      }
    }
  }

  // Reconstruct: _header first, then sections in base order, then any new-only sections
  const header = merged.get('_header') ?? '';
  const parts = [header];

  // Base sections in original order
  for (const name of baseSections.keys()) {
    if (name !== '_header' && merged.has(name)) {
      parts.push(merged.get(name));
    }
  }

  // New-only sections appended at end
  for (const name of incomingSections.keys()) {
    if (name !== '_header' && !baseSections.has(name) && merged.has(name)) {
      parts.push(merged.get(name));
    }
  }

  return parts.join('\n');
}

/**
 * Convenience: decide which sections to replace based on a diff.
 * Only replaces sections that actually changed, leaving user-edited unchanged sections intact.
 */
export function smartMerge(baseContent, incomingContent) {
  const diff = sectionDiff(baseContent, incomingContent);
  // Replace modified + add new sections, preserve unchanged (could be user-edited)
  return mergeSections(baseContent, incomingContent, 'selective', diff.modified);
}
