/**
 * Meta-Driven Test Skeleton Generator
 *
 * Deterministically generates test skeletons from ProjectMeta/1.0.
 * No LLM required — derives test structure from:
 *   - apps (which apps exist, their IDs for preview URL lookup)
 *   - sections (which pages exist, what services they consume)
 *   - services (what domain operations are available via methods list)
 *   - dataSchema (what data entities exist)
 *
 * Generated test cases are compatible with VLComponentTest "run" action steps.
 */

/**
 * Convert a section ID to a human-readable nav label.
 *   "AdminDashboardSection"  → "Dashboard"
 *   "CourseAdminSection"     → "Course Admin"
 *   "LibraryHomeSection"     → "Library Home"
 *   "LoginSection"           → "Login"
 */
function sectionIdToNavLabel(sectionId) {
  // Remove trailing "Section"
  let label = sectionId.replace(/Section$/, '');
  // Remove known app-role prefixes (AdminXxx → Xxx)
  label = label.replace(/^(Admin|Student|Teacher)([A-Z])/, '$2');
  // Split CamelCase
  label = label.replace(/([a-z])([A-Z])/g, '$1 $2');
  return label.trim() || sectionId;
}

/**
 * Detect CRUD capabilities from a service definition.
 * Falls back to ['list', 'create', 'update', 'delete'] when methods are empty.
 */
function detectServiceOperations(service) {
  const methods = service.methods || [];
  if (methods.length === 0) {
    return ['list', 'create', 'update', 'delete'];
  }
  const ops = new Set();
  for (const m of methods) {
    const name = (typeof m === 'string' ? m : m.name || '').toLowerCase();
    if (/^(get|list|fetch|find|search|query|load)/.test(name)) ops.add('list');
    if (/^(create|add|insert|register|save|post|new)/.test(name)) ops.add('create');
    if (/^(update|edit|modify|change|set|put|patch)/.test(name)) ops.add('update');
    if (/^(delete|remove|destroy)/.test(name)) ops.add('delete');
  }
  return ops.size ? [...ops] : ['list'];
}

/**
 * Generate an app-level smoke test.
 * Verifies the app loads and renders interactive elements.
 */
function generateAppSmokeTest(app, previewUrl, idx) {
  const appLabel = app.id.replace('App', ' App').trim();
  return {
    id: `WF_${idx}_${app.id}_Smoke`,
    name: `${appLabel} - Load & Render Smoke Test`,
    app: app.id,
    priority: 'P0',
    category: 'smoke',
    previewUrl,
    steps: [
      { action: 'open', url: previewUrl, description: `Open ${appLabel} preview` },
      { action: 'snapshot', description: 'Capture initial render state' },
      { action: 'screenshot', name: `${app.id}_smoke`, description: 'Screenshot after load' },
    ],
  };
}

/**
 * Generate a section navigation test.
 * Navigates to the section and asserts a key text element is visible.
 * If componentIndex provides a nav button instance-id for this section, use it directly.
 */
function generateSectionNavTest(section, app, previewUrl, idx, isHome, componentIndex) {
  const navLabel = sectionIdToNavLabel(section.id);
  const steps = [
    { action: 'open', url: previewUrl, description: `Open ${app.id} preview` },
  ];
  if (!isHome) {
    // Look up nav button instance-id from componentIndex if available
    const navBtn = componentIndex?.findNavButton(section.id);
    steps.push(navBtn
      ? { action: 'click', selector: `vlid:${navBtn}`, description: `Navigate to ${navLabel} via nav (vlid:${navBtn})` }
      : { action: 'click', selector: `text=${navLabel}`, description: `Navigate to ${navLabel} via nav menu` }
    );
  }
  steps.push({
    action: 'assert',
    selector: `text=${navLabel}`,
    assertType: 'visible',
    description: `Verify ${navLabel} section is visible`,
  });
  steps.push({
    action: 'screenshot',
    name: `${section.id}_nav`,
    description: `Capture ${navLabel} section`,
  });

  return {
    id: `WF_${idx}_${section.id}_Nav`,
    name: `${navLabel} - Navigation Test`,
    app: app.id,
    section: section.id,
    priority: 'P1',
    category: 'navigation',
    previewUrl,
    steps,
  };
}

/**
 * Generate a CRUD workflow test for a section + service.
 * Uses instance-id selectors (vlid:xxx) when componentIndex provides them.
 * Steps marked with [SKELETON] still need selector updates via VLComponentTest listIds.
 */
function generateCRUDWorkflowTest(section, serviceDomainId, app, previewUrl, ops, idx, componentIndex) {
  const navLabel = sectionIdToNavLabel(section.id);
  const entityName = serviceDomainId.replace(/Domain$/, '');
  const isHome = /dashboard|home/i.test(section.id);

  // Look up component IDs for this section if available
  const ci = componentIndex;
  const navBtn = ci?.findNavButton(section.id);
  const createBtn = ci?.findButton(section.id, /create|add|new/i);
  const editBtn = ci?.findButton(section.id, /edit|update|modify/i);
  const deleteBtn = ci?.findButton(section.id, /delete|remove/i);

  const steps = [
    { action: 'open', url: previewUrl, description: `Open ${app.id} preview` },
  ];

  if (!isHome) {
    steps.push(navBtn
      ? { action: 'click', selector: `vlid:${navBtn}`, description: `Navigate to ${navLabel} (vlid:${navBtn})` }
      : { action: 'click', selector: `text=${navLabel}`, description: `Navigate to ${navLabel}` }
    );
  }

  if (ops.includes('list')) {
    steps.push({
      action: 'snapshot',
      description: `Verify ${entityName} list — check snapshot for table/list instance-ids`,
    });
    steps.push({
      action: 'screenshot',
      name: `${section.id}_${entityName}_list`,
      description: `Capture ${entityName} list`,
    });
  }

  if (ops.includes('create')) {
    steps.push(createBtn
      ? { action: 'click', selector: `vlid:${createBtn}`, description: `Click create ${entityName} button (vlid:${createBtn})` }
      : { action: 'click', selector: `text=Create`, description: `[SKELETON] Click create ${entityName} — run listIds to get vlid:xxx selector` }
    );
    steps.push({
      action: 'screenshot',
      name: `${section.id}_${entityName}_create`,
      description: `Capture create ${entityName} form`,
    });
  }

  if (ops.includes('update')) {
    steps.push(editBtn
      ? { action: 'click', selector: `vlid:${editBtn}`, description: `Click edit ${entityName} (vlid:${editBtn})` }
      : { action: 'click', selector: `text=Edit`, description: `[SKELETON] Click edit ${entityName} — run listIds to get vlid:xxx selector` }
    );
  }

  if (ops.includes('delete')) {
    steps.push(deleteBtn
      ? { action: 'click', selector: `vlid:${deleteBtn}`, description: `Click delete ${entityName} (vlid:${deleteBtn}) — triggers confirm dialog` }
      : { action: 'click', selector: `text=Delete`, description: `[SKELETON] Click delete ${entityName} — run listIds to get vlid:xxx selector` }
    );
    // VL SysUI.showModal produces a native browser confirm — auto-accepted by VLComponentTest
  }

  const hasSkeletons = steps.some(s => s.description?.includes('[SKELETON]'));
  return {
    id: `WF_${idx}_${section.id}_${entityName}CRUD`,
    name: `${navLabel} - ${entityName} ${ops.join('+')} Workflow`,
    app: app.id,
    section: section.id,
    service: serviceDomainId,
    priority: 'P0',
    category: 'crud',
    previewUrl,
    ...(hasSkeletons ? {
      skeletonNotes: [
        '[SKELETON] selectors need vlid:xxx updates — run VLComponentTest {action:"listIds"} to get instance-ids',
        'Delete steps trigger SysUI.showModal confirm dialog — auto-accepted by VLComponentTest browser',
      ],
    } : {}),
    steps,
  };
}

/**
 * Build a ComponentIndex helper from interactiveElements metadata.
 * Allows test generation to look up instance-ids for buttons/nav by section + intent.
 *
 * @param {Array} sections - array of { id, interactiveElements: [{instanceId, type, name, label}] }
 */
function buildComponentIndex(sections) {
  // Map: sectionId → list of { instanceId, type, name, label }
  const bySection = new Map();
  for (const sec of (sections || [])) {
    const id = sec.id || sec.sectionId;
    if (!id) continue;
    const els = (sec.interactiveElements || sec.components || [])
      .filter(e => e.instanceId || e.compId)
      .map(e => ({
        instanceId: e.instanceId || e.compId,
        type: (e.type || '').toLowerCase(),
        name: (e.name || '').toLowerCase(),
        label: (e.label || '').toLowerCase(),
      }));
    if (els.length) bySection.set(id, els);
  }

  return {
    /** Find a nav/tab button for a given section by name heuristic */
    findNavButton(sectionId) {
      // Look in all sections for a button/tab whose name matches the section label
      const label = sectionIdToNavLabel(sectionId).toLowerCase();
      for (const [, els] of bySection) {
        const match = els.find(e =>
          (e.type === 'button' || e.type === 'tab' || e.type === 'navitem') &&
          (e.name.includes(label) || e.label.includes(label) || label.includes(e.name))
        );
        if (match) return match.instanceId;
      }
      return null;
    },
    /** Find a button in a specific section matching an intent regex */
    findButton(sectionId, intentRegex) {
      const els = bySection.get(sectionId) || [];
      const match = els.find(e =>
        e.type === 'button' &&
        (intentRegex.test(e.name) || intentRegex.test(e.label))
      );
      return match?.instanceId || null;
    },
  };
}

/**
 * Main skeleton generation function.
 *
 * @param {Object} meta - ProjectMeta/1.0 object
 * @param {Object} previewUrls - { AppId: "https://..." } map from project.json
 * @param {Object} [options]
 * @param {Array}  [options.interactiveElements] - sections with instanceId component info for ID-based selectors
 * @returns {{ testCases: TestCase[], summary: Object }}
 */
export function generateTestSkeleton(meta, previewUrls = {}, options = {}) {
  const testCases = [];
  let idx = 1;

  const apps = meta.apps || [];
  const sections = meta.sections || [];
  const services = meta.services || [];

  // Build service lookup: "Book" → BookDomain service, "BookDomain" → BookDomain service
  const serviceMap = new Map();
  for (const svc of services) {
    serviceMap.set(svc.domainId, svc);
    const shortKey = svc.domainId.replace(/Domain$/, '');
    serviceMap.set(shortKey, svc);
  }

  // Build component index for instance-id based selector generation
  const componentIndex = options.interactiveElements
    ? buildComponentIndex(options.interactiveElements)
    : null;

  // Identify system services to skip for CRUD tests
  const SYSTEM_SERVICES = new Set(['TOAST', 'SYSTEM', 'SysUI', 'Router', 'Nav']);

  // 1. App-level smoke tests (one per app with a preview URL)
  for (const app of apps) {
    const url = previewUrls[app.id];
    if (!url) continue;
    testCases.push(generateAppSmokeTest(app, url, String(idx++).padStart(3, '0')));
  }

  // 2. Per-app section navigation + CRUD tests
  for (const app of apps) {
    const url = previewUrls[app.id];
    if (!url) continue;

    // Match sections to this app by naming convention.
    // First try app.pages[].sections (populated by VLMetadata in newer projects).
    // Fall back to heuristic: sections whose ID contains the app role name.
    const appRole = app.id.replace('App', '').toLowerCase(); // "admin", "student", "teacher"
    const listedSectionIds = new Set(
      (app.pages || []).flatMap(p => p.sections || []).map(s => (typeof s === 'string' ? s : s.id))
    );

    let appSections;
    if (listedSectionIds.size > 0) {
      // Explicit listing from app.pages — most accurate
      appSections = sections.filter(s => listedSectionIds.has(s.id));
    } else {
      // Heuristic: section ID starts with OR contains the app role
      appSections = sections.filter(s => {
        const sid = s.id.toLowerCase();
        if (sid.startsWith(appRole) || sid.includes(appRole)) return true;
        // Shared sections appear in all apps
        if (/^(login|profile|notification)/.test(sid)) return true;
        return false;
      });
    }

    if (appSections.length === 0) continue;

    // Home section = first dashboard/home, or first section overall
    const homeSection =
      appSections.find(s => /dashboard|home/i.test(s.id)) || appSections[0];

    // Cap at 8 sections per app to avoid too many tests
    for (const section of appSections.slice(0, 8)) {
      const isHome = section === homeSection;
      const idxStr = String(idx++).padStart(3, '0');

      // Navigation test
      testCases.push(generateSectionNavTest(section, app, url, idxStr, isHome, componentIndex));

      // CRUD test for primary (first non-system) service
      const primaryService = (section.consumesServices || []).find(
        s => !SYSTEM_SERVICES.has(s)
      );
      if (primaryService) {
        const svc = serviceMap.get(primaryService) || serviceMap.get(primaryService + 'Domain');
        const ops = svc ? detectServiceOperations(svc) : ['list'];
        const domainId = svc?.domainId || (primaryService + 'Domain');
        const idxStr2 = String(idx++).padStart(3, '0');
        testCases.push(generateCRUDWorkflowTest(section, domainId, app, url, ops, idxStr2, componentIndex));
      }
    }
  }

  const summary = {
    projectName: meta.projectName || 'Unknown',
    generatedAt: new Date().toISOString(),
    totalTests: testCases.length,
    byCategory: {
      smoke:      testCases.filter(t => t.category === 'smoke').length,
      navigation: testCases.filter(t => t.category === 'navigation').length,
      crud:       testCases.filter(t => t.category === 'crud').length,
    },
    byPriority: {
      P0: testCases.filter(t => t.priority === 'P0').length,
      P1: testCases.filter(t => t.priority === 'P1').length,
      P2: testCases.filter(t => t.priority === 'P2').length,
    },
    appsWithUrls: apps.filter(a => previewUrls[a.id]).map(a => a.id),
    instructions: [
      '1. Run VLComponentTest {action:"listIds"} on each page to discover real selectors',
      '2. Update selectors marked [SKELETON] with actual button/nav text from the app',
      '3. Use VLMetaTest {action:"save"} to persist skeletons to .vl-code/test-skeletons/',
      '4. Run tests with VLComponentTest {action:"run", steps:[...]}',
      '5. Multi-option selectors (text=A, text=B) are invalid — pick ONE per step',
    ],
  };

  return { testCases, summary };
}
