/**
 * VL Workflow Engine — Registry Parser & Validator
 * Ported from Go: workflow/registry.go
 * Spec: v3.15
 */

/**
 * Parse a service signature:
 *   "ServiceName(param1(TYPE1), param2(TYPE2)) RETURN result1(TYPE1)"
 */
function parseServiceSignature(sig) {
  // Handle nested parens: ServiceName(param1(TYPE1), param2(TYPE2)) RETURN ...
  const nameEnd = sig.indexOf('(');
  if (nameEnd < 0) throw new Error(`Invalid service signature: ${sig}`);
  const name = sig.slice(0, nameEnd).trim();

  // Find the matching close paren for the outer params
  let depth = 0, paramsEnd = -1;
  for (let i = nameEnd; i < sig.length; i++) {
    if (sig[i] === '(') depth++;
    else if (sig[i] === ')') { depth--; if (depth === 0) { paramsEnd = i; break; } }
  }
  if (paramsEnd < 0) throw new Error(`Invalid service signature: ${sig}`);

  const paramsStr = sig.slice(nameEnd + 1, paramsEnd);
  const rest = sig.slice(paramsEnd + 1).trim();
  const returnsStr = rest.startsWith('RETURN') ? rest.slice(6).trim() : '';

  return {
    name,
    parameters: paramsStr ? parseNestedParamList(paramsStr) : [],
    returns: returnsStr ? parseNestedParamList(returnsStr) : []
  };
}

function parseParamList(str) {
  return str.split(',').map(s => s.trim()).filter(Boolean).map(s => {
    const m = s.match(/^(\w+)\(([^)]+)\)$/);
    return m ? { name: m[1], type: m[2] } : { name: s, type: 'STRING' };
  });
}

/** Split nested param list respecting parens: "a(TYPE), b(TYPE)" */
function parseNestedParamList(str) {
  const items = [];
  let depth = 0, start = 0;
  for (let i = 0; i <= str.length; i++) {
    if (i === str.length || (str[i] === ',' && depth === 0)) {
      const item = str.slice(start, i).trim();
      if (item) {
        const m = item.match(/^(\w+)\((.+)\)$/);
        items.push(m ? { name: m[1], type: m[2] } : { name: item, type: 'STRING' });
      }
      start = i + 1;
    } else if (str[i] === '(') depth++;
    else if (str[i] === ')') depth--;
  }
  return items;
}

/**
 * Parse a variable declaration: "$varName(TYPE)" or "$varName([TYPE])"
 */
function parseVariableDeclaration(decl) {
  const m = decl.match(/^(\$\w+)\((\[?\w+\]?)\)$/);
  if (!m) throw new Error(`Invalid variable declaration: ${decl}`);
  return { name: m[1], type: m[2] };
}

/**
 * Parse a param declaration: "paramName(TYPE)" or "paramName(TYPE) = default"
 */
function parseParamDeclaration(decl) {
  const m = decl.match(/^(\w+)\((\w+)\)(?:\s*=\s*(.+))?$/);
  if (!m) throw new Error(`Invalid param declaration: ${decl}`);
  const result = { name: m[1], type: m[2], default: undefined };
  if (m[3] !== undefined) {
    let def = m[3].trim();
    // Strip surrounding quotes
    if ((def.startsWith('"') && def.endsWith('"')) || (def.startsWith("'") && def.endsWith("'"))) {
      def = def.slice(1, -1);
    }
    result.default = coerceParamDefault(result.type, def);
  }
  return result;
}

function coerceParamDefault(type, val) {
  switch (type) {
    case 'INT': { const n = parseInt(val, 10); return isNaN(n) ? val : n; }
    case 'FLOAT': { const n = parseFloat(val); return isNaN(n) ? val : n; }
    case 'BOOL': return val === 'true' || val === '1';
    default: return val;
  }
}

/**
 * Validate a registry object.
 */
function validateRegistry(registry) {
  const errors = [];
  if (!registry) return errors;

  // Duplicate service names
  const svcNames = new Set();
  for (const sig of (registry.services || [])) {
    try {
      const parsed = parseServiceSignature(sig);
      if (svcNames.has(parsed.name)) errors.push(`Duplicate service: ${parsed.name}`);
      svcNames.add(parsed.name);
    } catch (e) { errors.push(e.message); }
  }

  // Duplicate API IDs
  const apiIds = new Set();
  for (const api of (registry.apis || [])) {
    if (!api.id) errors.push('API missing id');
    else if (apiIds.has(api.id)) errors.push(`Duplicate API: ${api.id}`);
    else apiIds.add(api.id);
    if (!api.url) errors.push(`API ${api.id}: url required`);
    if (api.method && !['GET','POST','PUT','PATCH','DELETE'].includes(api.method.toUpperCase())) {
      errors.push(`API ${api.id}: invalid method ${api.method}`);
    }
  }

  // Duplicate params
  const paramNames = new Set();
  for (const p of (registry.params || [])) {
    try {
      const parsed = parseParamDeclaration(p);
      if (paramNames.has(parsed.name)) errors.push(`Duplicate param: ${parsed.name}`);
      paramNames.add(parsed.name);
    } catch (e) { errors.push(e.message); }
  }

  // Duplicate vars
  const varNames = new Set();
  for (const v of (registry.vars || [])) {
    try {
      const parsed = parseVariableDeclaration(v);
      if (varNames.has(parsed.name)) errors.push(`Duplicate variable: ${parsed.name}`);
      varNames.add(parsed.name);
    } catch (e) { errors.push(e.message); }
  }

  return errors;
}

/**
 * Registry helper — lookup functions.
 */
class Registry {
  constructor(raw = {}) {
    this.raw = raw;
    this.services = raw.services || [];
    this.apis = raw.apis || [];
    this.components = raw.components || [];
    this.params = raw.params || [];
    this.vars = raw.vars || [];
    this.files = raw.files || { inputs: [], artifacts: [] };
    this.docs = raw.docs || {};
    this.schemas = raw.schemas || {};
  }

  hasDoc(id) { return id in this.docs; }
  getDocDescription(id) { return this.docs[id] || null; }

  getServiceSignature(name) {
    const sig = this.services.find(s => s.startsWith(name + '('));
    return sig ? parseServiceSignature(sig) : null;
  }

  hasComponent(id) { return this.components.includes(id); }

  getAPIDefinition(id) {
    return this.apis.find(a => a.id === id) || null;
  }
  hasAPI(id) { return this.apis.some(a => a.id === id); }

  getVariableDeclarations() {
    const map = {};
    for (const v of this.vars) {
      try { const d = parseVariableDeclaration(v); map[d.name] = d; } catch {}
    }
    return map;
  }

  getParamDeclarations() {
    const map = {};
    for (const p of this.params) {
      try { const d = parseParamDeclaration(p); map[d.name] = d; } catch {}
    }
    return map;
  }

  isInputPathAllowed(path) { return matchesAnyPattern(path, this.files.inputs || []); }
  isArtifactPathAllowed(path) { return matchesAnyPattern(path, this.files.artifacts || []); }

  hasSchema(id) { return id in this.schemas; }
  getSchema(id) { return this.schemas[id] || null; }
}

function matchesAnyPattern(path, patterns) {
  return patterns.some(p => matchPattern(path, p));
}

function matchPattern(path, pattern) {
  // Convert glob to regex: * → [^/]*, ** → .*
  const re = pattern
    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    .replace(/\\\*\\\*/g, '.*')
    .replace(/\\\*/g, '[^/]*');
  return new RegExp('^' + re + '$').test(path);
}

module.exports = {
  parseServiceSignature, parseVariableDeclaration, parseParamDeclaration,
  coerceParamDefault, validateRegistry, Registry
};
