use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;

impl<'a> ValidationContext<'a> {
  pub(crate) fn validate_family(
    &self,
    result: &mut ValidationResult,
  ) -> Result<bool, ValidationError> {
    if self.schema.family.is_some() {
      let conflicts = self.schema.type_.is_some()
        || self.schema.properties.is_some()
        || self.schema.required.is_some()
        || self.schema.additional_properties.is_some()
        || self.schema.items.is_some()
        || self.schema.r#ref.is_some()
        || self.schema.one_of.is_some()
        || self.schema.all_of.is_some()
        || self.schema.enum_.is_some()
        || self.schema.const_.is_some();

      if conflicts {
        result.errors.push(ValidationError {
          code: "INVALID_SCHEMA".to_string(),
          message: "$family must be used exclusively without other constraints".to_string(),
          path: self.path.to_string(),
        });
        // Short-circuit: the schema formulation is broken
        return Ok(false);
      }
    }

    if let Some(family_target) = &self.schema.family {
      // The descendants map is keyed by the schema's own $id, not the target string.
      if let Some(schema_id) = &self.schema.id
        && let Some(descendants) = self.db.descendants.get(schema_id)
      {
        // Validate against all descendants simulating strict oneOf logic
        let mut passed_candidates: Vec<(String, usize, ValidationResult)> = Vec::new();

        // The target itself is also an implicitly valid candidate
        let mut all_targets = vec![family_target.clone()];
        all_targets.extend(descendants.clone());

        for child_id in &all_targets {
          if let Some(child_schema) = self.db.schemas.get(child_id) {
            let derived = self.derive(
              child_schema,
              self.instance,
              &self.path,
              self.overrides.clone(),
              self.extensible,
              self.reporter, // Inherit parent reporter flag, do not bypass strictness!
            );

            // Explicitly run validate_scoped to accurately test candidates with strictness checks enabled
            let res = derived.validate_scoped()?;

            if res.is_valid() {
              let depth = self.db.depths.get(child_id).copied().unwrap_or(0);
              passed_candidates.push((child_id.clone(), depth, res));
            }
          }
        }

        if passed_candidates.len() == 1 {
          result.merge(passed_candidates.pop().unwrap().2);
        } else if passed_candidates.is_empty() {
          result.errors.push(ValidationError {
            code: "NO_FAMILY_MATCH".to_string(),
            message: format!(
              "Payload did not match any descendants of family '{}'",
              family_target
            ),
            path: self.path.to_string(),
          });
        } else {
          // Apply depth heuristic tie-breaker
          let mut best_depth: Option<usize> = None;
          let mut ambiguous = false;
          let mut best_res = None;

          for (_, depth, res) in passed_candidates.into_iter() {
            if let Some(current_best) = best_depth {
              if depth > current_best {
                best_depth = Some(depth);
                best_res = Some(res);
                ambiguous = false; // Broke the tie
              } else if depth == current_best {
                ambiguous = true; // Tie at the highest level
              }
            } else {
              best_depth = Some(depth);
              best_res = Some(res);
            }
          }

          if !ambiguous {
            if let Some(res) = best_res {
              result.merge(res);
              return Ok(true);
            }
          }

          result.errors.push(ValidationError {
            code: "AMBIGUOUS_FAMILY_MATCH".to_string(),
            message: format!(
              "Payload matched multiple descendants of family '{}' without a clear depth winner",
              family_target
            ),
            path: self.path.to_string(),
          });
        }
      }
    }

    Ok(true)
  }

  pub(crate) fn validate_refs(
    &self,
    result: &mut ValidationResult,
  ) -> Result<bool, ValidationError> {
    // 1. Core $ref logic relies on the fast O(1) map to allow cycles and proper nesting
    if let Some(ref_str) = &self.schema.r#ref {
      if let Some(global_schema) = self.db.schemas.get(ref_str) {
        let mut new_overrides = self.overrides.clone();
        if let Some(props) = &self.schema.properties {
          new_overrides.extend(props.keys().map(|k| k.to_string()));
        }

        let mut shadow = self.derive(
          global_schema,
          self.instance,
          &self.path,
          new_overrides,
          self.extensible,
          true,
        );
        shadow.root = global_schema;
        result.merge(shadow.validate()?);
      } else {
        result.errors.push(ValidationError {
          code: "REF_RESOLUTION_FAILED".to_string(),
          message: format!(
            "Reference pointer to '{}' was not found in schema registry",
            ref_str
          ),
          path: self.path.to_string(),
        });
      }
    }
    Ok(true)
  }
}
