'use strict';

/** @typedef {import('./validator-types').Issue} Issue */

/**
 * Cross-model validator. Verifies relation targets resolve, references fields
 * exist, and belongsTo references point to primary keys. Successful relations
 * are augmented with `_targetSchemaName`, `_targetTableName`, and a rewritten
 * fully-qualified `target` so downstream validators (type compatibility,
 * circular detection) can consume schema-qualified information without
 * re-resolving.
 *
 * Returns an array of Issue objects (empty when all relations validate).
 *
 * @param {Map<string, object>} models
 * @returns {Issue[]}
 */
function validateCrossModel(models) {
  const issues = [];
  const byUnqualified = buildUnqualifiedIndex(models);

  for (const [sourceQualified, ir] of models) {
    for (const [relName, rel] of Object.entries(ir.relations)) {
      const resolution = resolveTarget(sourceQualified, ir, relName, rel.target, models, byUnqualified);
      if (resolution.issue) {
        issues.push(decorateIssue(resolution.issue, ir, relName));
        continue;
      }

      const resolvedQualified = resolution.qualified;
      const targetModel = models.get(resolvedQualified);

      // Augment the relation with resolved target info as soon as the target
      // itself is known. Downstream validators rely on this even when later
      // checks (references / PK) fail.
      rel._targetSchemaName = targetModel.schemaName || null;
      rel._targetTableName = targetModel.tableName;
      rel.target = resolvedQualified;

      if (!targetModel.fields[rel.references]) {
        issues.push(decorateIssue({
          severity: 'error',
          code: 'relation-unknown-references-field',
          message: `Table '${sourceQualified}': relation '${relName}' references unknown field '${rel.references}' in target table '${resolvedQualified}'`
        }, ir, relName));
        continue;
      }

      if (rel.type === 'belongsTo' && !isPrimaryKey(targetModel, rel.references)) {
        issues.push(decorateIssue({
          severity: 'error',
          code: 'relation-not-primary-key',
          message: `Table '${sourceQualified}': relation '${relName}' references field '${rel.references}' in '${resolvedQualified}' which is not a primary key`
        }, ir, relName));
      }
    }
  }

  return issues;
}

function buildUnqualifiedIndex(models) {
  const index = new Map();
  for (const [qualified, ir] of models) {
    const list = index.get(ir.tableName);
    if (list) {
      list.push(qualified);
    } else {
      index.set(ir.tableName, [qualified]);
    }
  }
  return index;
}

/**
 * @returns {{ qualified: string, issue: null } | { qualified: null, issue: Issue }}
 */
function resolveTarget(sourceQualified, sourceIr, relName, target, models, byUnqualified) {
  if (typeof target !== 'string' || target === '') {
    return {
      qualified: null,
      issue: {
        severity: 'error',
        code: 'relation-invalid-target',
        message: `Table '${sourceQualified}': relation '${relName}' has invalid target '${target}'`
      }
    };
  }

  if (target.includes('.')) {
    if (!models.has(target)) {
      return {
        qualified: null,
        issue: {
          severity: 'error',
          code: 'relation-unknown-target',
          message: `Table '${sourceQualified}': relation '${relName}' targets unknown table '${target}'`
        }
      };
    }
    return { qualified: target, issue: null };
  }

  // Unqualified: locality first (same schema as source), then unique cross-schema match.
  const sourceSchema = sourceIr.schemaName || null;
  const localQualified = sourceSchema ? `${sourceSchema}.${target}` : target;
  if (models.has(localQualified)) {
    return { qualified: localQualified, issue: null };
  }

  const candidates = byUnqualified.get(target) || [];
  if (candidates.length === 0) {
    return {
      qualified: null,
      issue: {
        severity: 'error',
        code: 'relation-unknown-target',
        message: `Table '${sourceQualified}': relation '${relName}' targets unknown table '${target}'`
      }
    };
  }
  if (candidates.length === 1) {
    return { qualified: candidates[0], issue: null };
  }
  return {
    qualified: null,
    issue: {
      severity: 'error',
      code: 'relation-ambiguous-target',
      message: `Table '${sourceQualified}': relation '${relName}' target '${target}' is ambiguous, found in: ${candidates.join(', ')}. Use qualified target like '${candidates[0]}'.`
    }
  };
}

function isPrimaryKey(model, fieldName) {
  // Skip when target has no PK at all — single-model validator should have flagged this earlier.
  if (!Array.isArray(model.primaryKey) || model.primaryKey.length === 0) return true;
  return model.primaryKey.includes(fieldName);
}

function decorateIssue(issue, ir, relName) {
  const decorated = {
    severity: issue.severity,
    code: issue.code,
    message: issue.message
  };
  if (ir._filePath) decorated.file = ir._filePath;
  if (ir.qualifiedName) decorated.table = ir.qualifiedName;
  if (relName) decorated.relation = relName;
  return decorated;
}

module.exports = { validateCrossModel };
