/**
 * PR-37 Part E — Domain Validator
 *
 * Validates a requirement package (epics, features, userStories) against
 * a domain from the registry, producing coverage, consistency, and
 * completeness scores, plus actionable recommendations.
 *
 * @module pr-37-domain-intelligence/domain-validator
 * @since PR-37
 */

import { getDomain, listDomains } from "./domain-registry.mjs";

// ══════════════════════════════════════════════════════════
// Types / typedefs (JSDoc only)
// ══════════════════════════════════════════════════════════

/**
 * @typedef {object} ValidationReport
 * @property {number} coverageScore - 0-100: % of domain epics covered by generated epics
 * @property {number} consistencyScore - 0-100: % of features matching domain expectations
 * @property {number} completenessScore - 0-100: % of domain coreEpics present
 * @property {string[]} missingEpics - Domain epics NOT present in generated set
 * @property {string[]} extraEpics - Generated epics NOT found in domain
 * @property {string[]} recommendations - Actionable suggestions
 * @property {"pass"|"conditional"|"fail"} overallVerdict - Final verdict
 */

// ══════════════════════════════════════════════════════════
// Internal helpers
// ══════════════════════════════════════════════════════════

/**
 * Normalize text for fuzzy comparison.
 *
 * @param {string} text
 * @returns {string}
 */
function normalize(text) {
  return text.trim().toLowerCase().replace(/[\s\u3000]+/g, "");
}

/**
 * Check if a generated epic title matches a domain epic title.
 *
 * @param {string} generatedTitle - Title from generated epics
 * @param {object[]} domainEpics - Domain coreEpics array
 * @returns {boolean}
 */
function matchesDomainEpic(generatedTitle, domainEpics) {
  const g = normalize(generatedTitle);
  return domainEpics.some(
    (de) =>
      normalize(de.title) === g ||
      normalize(de.title).includes(g) ||
      g.includes(normalize(de.title))
  );
}

/**
 * Check if a generated feature title/name matches a domain feature name.
 *
 * @param {string} generatedFeatureTitle - Generated feature name
 * @param {object} domainFeatures - Domain coreFeatures object
 * @returns {boolean}
 */
function matchesDomainFeature(generatedFeatureTitle, domainFeatures) {
  const g = normalize(generatedFeatureTitle);
  const allDomainFeatureNames = Object.values(domainFeatures).flat();
  return allDomainFeatureNames.some(
    (dfn) =>
      normalize(dfn) === g ||
      normalize(dfn).includes(g) ||
      g.includes(normalize(dfn))
  );
}

// ══════════════════════════════════════════════════════════
// Public API
// ══════════════════════════════════════════════════════════

/**
 * Validate a requirement package against a target domain.
 *
 * @param {object} requirementPackage - The requirement package to validate
 * @param {object[]} [requirementPackage.epics] - Generated epics
 * @param {object[]} [requirementPackage.features] - Generated features
 * @param {object[]} [requirementPackage.userStories] - Generated user stories
 * @param {string} domainName - Target domain name for validation
 * @returns {ValidationReport}
 *
 * @example
 * const report = validateRequirements({ epics, features, userStories }, "MES");
 * // => { coverageScore: 80, consistencyScore: 75, completenessScore: 70, ... }
 */
export function validateRequirements(requirementPackage, domainName) {
  // ── Default empty result ──────────────────────────────
  const emptyResult = {
    coverageScore: 0,
    consistencyScore: 0,
    completenessScore: 0,
    missingEpics: [],
    extraEpics: [],
    recommendations: [],
    overallVerdict: "fail",
  };

  if (!requirementPackage || typeof requirementPackage !== "object") {
    return {
      ...emptyResult,
      recommendations: ["Requirement package is empty or invalid"],
    };
  }

  if (!domainName) {
    return {
      ...emptyResult,
      recommendations: ["No domain specified for validation"],
    };
  }

  const domain = getDomain(domainName);
  if (!domain) {
    return {
      ...emptyResult,
      recommendations: [
        `Domain "${domainName}" not found in registry. Available: ${listDomains().join(", ")}`,
      ],
    };
  }

  // Extract inputs with defaults
  const generatedEpics = requirementPackage.epics || [];
  const generatedFeatures = requirementPackage.features || [];
  const generatedUserStories = requirementPackage.userStories || [];

  const domainEpics = domain.coreEpics || [];
  const domainFeatures = domain.coreFeatures || {};

  // ══ 1. Completeness: % of domain coreEpics present ═══
  const coveredDomainTitles = domainEpics.filter((de) =>
    matchesDomainEpic(de.title, generatedEpics)
  );
  const missingEpics = domainEpics
    .filter((de) => !matchesDomainEpic(de.title, generatedEpics))
    .map((de) => de.title);

  const completenessScore =
    domainEpics.length > 0
      ? Math.round((coveredDomainTitles.length / domainEpics.length) * 100)
      : 0;

  // ══ 2. Coverage: % of generated epics matching domain ═══
  const coveredGeneratedEpics = generatedEpics.filter((ge) =>
    matchesDomainEpic(ge.title, domainEpics)
  );
  const extraEpics = generatedEpics
    .filter((ge) => !matchesDomainEpic(ge.title, domainEpics))
    .map((ge) => ge.title);

  const coverageScore =
    generatedEpics.length > 0
      ? Math.round((coveredGeneratedEpics.length / generatedEpics.length) * 100)
      : 0;

  // ══ 3. Consistency: features matching domain ════════
  const consistentFeatures = generatedFeatures.filter((gf) =>
    matchesDomainFeature(gf.title, domainFeatures)
  );
  const consistencyScore =
    generatedFeatures.length > 0
      ? Math.round((consistentFeatures.length / generatedFeatures.length) * 100)
      : 100; // If no features, consider it consistent (trivially)

  // ══ 4. Generate recommendations ═══════════════════════
  const recommendations = [];

  if (missingEpics.length > 0) {
    recommendations.push(
      `Add missing epics from ${domain.displayName}: ${missingEpics.join(", ")}`
    );
  }

  if (extraEpics.length > 0) {
    recommendations.push(
      `Remove or align extra epics not in ${domain.displayName}: ${extraEpics.join(", ")}`
    );
  }

  if (consistencyScore < 70 && generatedFeatures.length > 0) {
    recommendations.push(
      `Feature consistency is low (${consistencyScore}%). Review feature titles against ${domain.displayName} core features.`
    );
  }

  if (completenessScore < 50) {
    recommendations.push(
      `Completeness is critically low (${completenessScore}%). Consider increasing domain epic coverage for ${domain.displayName}.`
    );
  }

  // Recommendations for user stories (if available)
  if (generatedUserStories.length > 0 && generatedEpics.length > 0) {
    const storyCountPerEpic = generatedUserStories.filter((s) => s.epicId).length;
    if (storyCountPerEpic < generatedEpics.length) {
      recommendations.push(
        "Ensure each epic has at least one associated user story."
      );
    }
  }

  if (generatedEpics.length === 0) {
    recommendations.push(
      `No epics generated. Start by adding epics for ${domain.displayName}.`
    );
  }

  if (!recommendations.length) {
    recommendations.push(
      `${domain.displayName} requirement package looks well-aligned with domain expectations.`
    );
  }

  // ══ 5. Overall verdict ═══════════════════════════════
  const overallVerdict = computeVerdict(completenessScore, coverageScore, consistencyScore, missingEpics.length);

  return {
    coverageScore,
    consistencyScore,
    completenessScore,
    missingEpics,
    extraEpics,
    recommendations,
    overallVerdict,
  };
}

/**
 * Compute the overall verdict based on component scores.
 *
 * @param {number} completeness - Completeness score 0-100
 * @param {number} coverage - Coverage score 0-100
 * @param {number} consistency - Consistency score 0-100
 * @param {number} missingCount - Number of missing epics
 * @returns {"pass"|"conditional"|"fail"}
 */
function computeVerdict(completeness, coverage, consistency, missingCount) {
  // Fail: critically missing domain content
  if (completeness < 30 || missingCount >= 5) return "fail";

  // Pass: all scores high
  if (completeness >= 80 && coverage >= 80 && consistency >= 80) return "pass";

  // Conditional: some gaps but not critical
  if (completeness >= 30) return "conditional";

  return "fail";
}

/**
 * Validate an epic array alone against a domain (lightweight).
 *
 * @param {object[]} epics - Generated epics
 * @param {string} domainName - Target domain
 * @returns {ValidationReport}
 */
export function validateEpics(epics, domainName) {
  return validateRequirements({ epics, features: [], userStories: [] }, domainName);
}

/**
 * Validate features alone against a domain (lightweight).
 *
 * @param {object[]} features - Generated features
 * @param {string} domainName - Target domain
 * @returns {object} Partial validation result with consistencyScore
 */
export function validateFeatures(features, domainName) {
  const result = validateRequirements(
    { epics: [], features, userStories: [] },
    domainName
  );
  return {
    consistencyScore: result.consistencyScore,
    consistencyDetails: `Feature consistency with ${domainName}: ${result.consistencyScore}%`,
    recommendations: result.recommendations.filter((r) =>
      r.toLowerCase().includes("feature") || r.toLowerCase().includes("consistency")
    ),
  };
}
