/// Path resolver — port of PHP `Path_Resolver`.
///
/// Supports dot-notation and bracket-notation paths with `*` wildcards:
///   `"props.*.value"` — matches all keys at `props.*`
///   `"items[*].color"` — matches all array indices
///   `"settings.color"` — concrete path

use serde_json::Value;

#[derive(Debug, Clone)]
pub struct PathMatch {
    /// The concrete resolved path string (e.g. `"props.foo.value"`).
    pub path: String,
    /// Ordered list of wildcard bindings that were matched.
    pub wildcard_values: Vec<WildcardBinding>,
}

#[derive(Debug, Clone)]
pub struct WildcardBinding {
    pub key: PathKey,
    pub segment_type: SegmentType,
}

#[derive(Debug, Clone)]
pub enum PathKey {
    Str(String),
    Idx(usize),
}

#[derive(Debug, Clone, PartialEq)]
pub enum SegmentType {
    Key,
    Index,
}

#[derive(Debug, Clone)]
struct Segment {
    value: String,
    seg_type: SegmentType,
    is_wildcard: bool,
}

// ─── Public API ───────────────────────────────────────────────────────────────

/// Resolve a path pattern against `data`. Returns all matching concrete paths.
/// `allow_missing_leaf`: if true, a missing final key is still returned (for set ops).
pub fn resolve(pattern: &str, data: &Value, allow_missing_leaf: bool) -> Vec<PathMatch> {
    let segments = match parse_path(pattern) {
        Ok(s) => s,
        Err(_) => return vec![],
    };
    resolve_segments(&segments, data, &[], "", allow_missing_leaf)
}

/// Resolve a pattern but substitute wildcard positions from `wildcard_values`.
pub fn resolve_with_wildcard_binding(
    pattern: &str,
    data: &Value,
    wildcard_values: &[WildcardBinding],
) -> Option<String> {
    let segments = parse_path(pattern).ok()?;
    let mut idx = 0;
    resolve_with_binding(&segments, data, "", wildcard_values, &mut idx)
}

/// Get the value at a concrete path.
pub fn get<'a>(path: &str, data: &'a Value) -> Option<&'a Value> {
    let segments = parse_path(path).ok()?;
    let mut current = data;
    for seg in &segments {
        current = match &seg.seg_type {
            SegmentType::Key => current.get(&seg.value)?,
            SegmentType::Index => {
                let i: usize = seg.value.parse().ok()?;
                current.get(i)?
            }
        };
    }
    Some(current)
}

// ─── Internal ─────────────────────────────────────────────────────────────────

fn parse_path(path: &str) -> Result<Vec<Segment>, String> {
    let mut segments = Vec::new();
    let chars: Vec<char> = path.chars().collect();
    let len = chars.len();
    let mut i = 0;
    let mut current = String::new();

    while i < len {
        match chars[i] {
            '.' => {
                flush(&mut segments, &mut current, SegmentType::Key);
            }
            '[' => {
                flush(&mut segments, &mut current, SegmentType::Key);
                let end = path[i..].find(']').map(|p| i + p);
                let end = end.ok_or_else(|| format!("Unmatched '[' in path: {}", path))?;
                let index = path[i + 1..end].to_string();
                segments.push(make_segment(index, SegmentType::Index));
                i = end;
            }
            ']' => return Err(format!("Unmatched ']' in path: {}", path)),
            c => current.push(c),
        }
        i += 1;
    }
    flush(&mut segments, &mut current, SegmentType::Key);
    Ok(segments)
}

fn flush(segments: &mut Vec<Segment>, current: &mut String, seg_type: SegmentType) {
    if !current.is_empty() {
        segments.push(make_segment(std::mem::take(current), seg_type));
    }
}

fn make_segment(value: String, seg_type: SegmentType) -> Segment {
    let is_wildcard = value == "*";
    Segment { value, seg_type, is_wildcard }
}

fn resolve_segments(
    segments: &[Segment],
    data: &Value,
    wildcard_values: &[WildcardBinding],
    current_path: &str,
    allow_missing_leaf: bool,
) -> Vec<PathMatch> {
    if segments.is_empty() {
        return vec![PathMatch {
            path: current_path.to_string(),
            wildcard_values: wildcard_values.to_vec(),
        }];
    }

    if !data.is_object() && !data.is_array() {
        return vec![];
    }

    let (head, tail) = segments.split_first().unwrap();
    let is_last = tail.is_empty();

    if head.is_wildcard {
        return resolve_wildcard(head, tail, data, wildcard_values, current_path, allow_missing_leaf);
    }

    resolve_concrete(head, tail, data, wildcard_values, current_path, is_last, allow_missing_leaf)
}

fn resolve_wildcard(
    seg: &Segment,
    rest: &[Segment],
    data: &Value,
    wc: &[WildcardBinding],
    current_path: &str,
    allow_missing_leaf: bool,
) -> Vec<PathMatch> {
    let mut results = Vec::new();

    let iter: Vec<(String, &Value)> = match data {
        Value::Object(obj) if seg.seg_type == SegmentType::Key => {
            obj.iter().map(|(k, v)| (k.clone(), v)).collect()
        }
        Value::Array(arr) if seg.seg_type == SegmentType::Index => {
            arr.iter().enumerate().map(|(i, v)| (i.to_string(), v)).collect()
        }
        _ => return vec![],
    };

    for (key, val) in iter {
        let mut new_wc = wc.to_vec();
        let path_key = if seg.seg_type == SegmentType::Index {
            PathKey::Idx(key.parse().unwrap_or(0))
        } else {
            PathKey::Str(key.clone())
        };
        new_wc.push(WildcardBinding { key: path_key, segment_type: seg.seg_type.clone() });

        let new_path = append_path(current_path, &key, &seg.seg_type);
        results.extend(resolve_segments(rest, val, &new_wc, &new_path, allow_missing_leaf));
    }

    results
}

fn resolve_concrete(
    seg: &Segment,
    rest: &[Segment],
    data: &Value,
    wc: &[WildcardBinding],
    current_path: &str,
    is_last: bool,
    allow_missing_leaf: bool,
) -> Vec<PathMatch> {
    let key = &seg.value;
    let exists = match data {
        Value::Object(obj) => obj.contains_key(key.as_str()),
        Value::Array(arr) => key.parse::<usize>().map(|i| i < arr.len()).unwrap_or(false),
        _ => false,
    };

    if !exists && !(allow_missing_leaf && is_last) {
        return vec![];
    }

    let new_path = append_path(current_path, key, &seg.seg_type);

    if is_last {
        return vec![PathMatch {
            path: new_path,
            wildcard_values: wc.to_vec(),
        }];
    }

    let next = match data {
        Value::Object(obj) => obj.get(key.as_str()),
        Value::Array(arr) => key.parse::<usize>().ok().and_then(|i| arr.get(i)),
        _ => None,
    };

    match next {
        Some(v) => resolve_segments(rest, v, wc, &new_path, allow_missing_leaf),
        None => vec![],
    }
}

fn resolve_with_binding(
    segments: &[Segment],
    data: &Value,
    prefix: &str,
    wc: &[WildcardBinding],
    wc_idx: &mut usize,
) -> Option<String> {
    if segments.is_empty() {
        return Some(prefix.to_string());
    }

    let (head, tail) = segments.split_first().unwrap();

    if head.is_wildcard {
        let binding = wc.get(*wc_idx)?;
        *wc_idx += 1;
        let key = match &binding.key {
            PathKey::Str(s) => s.clone(),
            PathKey::Idx(i) => i.to_string(),
        };
        let exists = match data {
            Value::Object(obj) => obj.contains_key(key.as_str()),
            Value::Array(arr) => key.parse::<usize>().map(|i| i < arr.len()).unwrap_or(false),
            _ => false,
        };
        if !exists {
            return None;
        }
        let new_prefix = append_path(prefix, &key, &head.seg_type);
        let next = match data {
            Value::Object(obj) => obj.get(key.as_str())?,
            Value::Array(arr) => arr.get(key.parse::<usize>().ok()?)?,
            _ => return None,
        };
        return resolve_with_binding(tail, next, &new_prefix, wc, wc_idx);
    }

    let key = &head.value;
    let new_prefix = append_path(prefix, key, &head.seg_type);
    let next = match data {
        Value::Object(obj) => obj.get(key.as_str())?,
        Value::Array(arr) => arr.get(key.parse::<usize>().ok()?)?,
        _ => return None,
    };
    resolve_with_binding(tail, next, &new_prefix, wc, wc_idx)
}

fn append_path(prefix: &str, key: &str, seg_type: &SegmentType) -> String {
    if *seg_type == SegmentType::Index {
        return format!("{}[{}]", prefix, key);
    }
    if prefix.is_empty() {
        key.to_string()
    } else {
        format!("{}.{}", prefix, key)
    }
}
