use std::collections::BTreeSet;

use crate::domain::{meaningful_longest_common_prefix, meaningful_longest_common_suffix};
use crate::reduced_product::{
    concatenate_reduced_product_class_values, intersect_reduced_product_class_values,
    join_reduced_product_class_values, reduced_product_class_value_is_subset,
};
use crate::selector_projection::abstract_value_matches_string;
use crate::{
    AbstractClassValueProvenanceV0, AbstractClassValueV0, bottom_class_value,
    enumerate_finite_class_values, finite_set_class_value, prefix_class_value,
    prefix_suffix_class_value, suffix_class_value, top_class_value,
};

pub fn intersect_abstract_class_values(
    left: &AbstractClassValueV0,
    right: &AbstractClassValueV0,
) -> AbstractClassValueV0 {
    match (left, right) {
        (AbstractClassValueV0::Bottom, _) | (_, AbstractClassValueV0::Bottom) => {
            bottom_class_value()
        }
        (AbstractClassValueV0::Top, value) | (value, AbstractClassValueV0::Top) => value.clone(),
        _ => intersect_non_top_class_values(left, right),
    }
}

pub fn join_abstract_class_values(
    left: &AbstractClassValueV0,
    right: &AbstractClassValueV0,
) -> AbstractClassValueV0 {
    if abstract_class_value_is_subset(left, right) {
        return right.clone();
    }
    if abstract_class_value_is_subset(right, left) {
        return left.clone();
    }

    match (
        enumerate_finite_class_values(left),
        enumerate_finite_class_values(right),
    ) {
        (Some(left_values), Some(right_values)) => {
            return finite_set_class_value(left_values.into_iter().chain(right_values));
        }
        (Some(values), None)
            if values
                .iter()
                .all(|value| abstract_value_matches_string(right, value)) =>
        {
            return right.clone();
        }
        (None, Some(values))
            if values
                .iter()
                .all(|value| abstract_value_matches_string(left, value)) =>
        {
            return left.clone();
        }
        _ => {}
    }

    match (left, right) {
        (
            AbstractClassValueV0::Prefix {
                prefix: left_prefix,
                ..
            },
            AbstractClassValueV0::Prefix {
                prefix: right_prefix,
                ..
            },
        ) => {
            let prefix =
                meaningful_longest_common_prefix(&[left_prefix.clone(), right_prefix.clone()]);
            if !prefix.is_empty() {
                return prefix_class_value(
                    prefix,
                    Some(AbstractClassValueProvenanceV0::PrefixJoinLcp),
                );
            }
        }
        (
            AbstractClassValueV0::Suffix {
                suffix: left_suffix,
                ..
            },
            AbstractClassValueV0::Suffix {
                suffix: right_suffix,
                ..
            },
        ) => {
            let suffix =
                meaningful_longest_common_suffix(&[left_suffix.clone(), right_suffix.clone()]);
            if !suffix.is_empty() {
                return suffix_class_value(
                    suffix,
                    Some(AbstractClassValueProvenanceV0::SuffixJoinLcs),
                );
            }
        }
        _ => {}
    }

    join_reduced_product_class_values(left, right).unwrap_or_else(top_class_value)
}

pub fn concatenate_abstract_class_values(
    left: &AbstractClassValueV0,
    right: &AbstractClassValueV0,
) -> AbstractClassValueV0 {
    match (left, right) {
        (AbstractClassValueV0::Bottom, _) | (_, AbstractClassValueV0::Bottom) => {
            return bottom_class_value();
        }
        (AbstractClassValueV0::Top, _) | (_, AbstractClassValueV0::Top) => {
            return top_class_value();
        }
        _ => {}
    }

    if let (Some(left_values), Some(right_values)) = (
        enumerate_finite_class_values(left),
        enumerate_finite_class_values(right),
    ) {
        return finite_set_class_value(left_values.into_iter().flat_map(|left_value| {
            right_values
                .iter()
                .map(move |right_value| format!("{left_value}{right_value}"))
        }));
    }

    match (left, right) {
        (AbstractClassValueV0::Exact { value }, AbstractClassValueV0::Prefix { prefix, .. }) => {
            prefix_class_value(format!("{value}{prefix}"), None)
        }
        (AbstractClassValueV0::Exact { value }, AbstractClassValueV0::Suffix { suffix, .. }) => {
            prefix_suffix_class_value(value, suffix, Some(value.len() + suffix.len()), None)
        }
        (
            AbstractClassValueV0::Exact { value },
            AbstractClassValueV0::PrefixSuffix {
                prefix,
                suffix,
                min_length,
                ..
            },
        ) => prefix_suffix_class_value(
            format!("{value}{prefix}"),
            suffix,
            Some(value.len() + min_length),
            None,
        ),
        (AbstractClassValueV0::Prefix { prefix, .. }, AbstractClassValueV0::Exact { value }) => {
            prefix_suffix_class_value(prefix, value, Some(prefix.len() + value.len()), None)
        }
        (
            AbstractClassValueV0::Prefix { prefix, .. },
            AbstractClassValueV0::FiniteSet { values },
        ) => {
            let suffix = meaningful_longest_common_suffix(values);
            if suffix.is_empty() {
                prefix_class_value(prefix, None)
            } else {
                prefix_suffix_class_value(
                    prefix,
                    suffix.clone(),
                    Some(prefix.len() + suffix.len()),
                    None,
                )
            }
        }
        (AbstractClassValueV0::Prefix { prefix, .. }, AbstractClassValueV0::Prefix { .. }) => {
            prefix_class_value(prefix, None)
        }
        (
            AbstractClassValueV0::Prefix { prefix, .. },
            AbstractClassValueV0::Suffix { suffix, .. },
        )
        | (
            AbstractClassValueV0::Prefix { prefix, .. },
            AbstractClassValueV0::PrefixSuffix { suffix, .. },
        ) => prefix_suffix_class_value(prefix, suffix, Some(prefix.len() + suffix.len()), None),
        (
            AbstractClassValueV0::FiniteSet { values },
            AbstractClassValueV0::Prefix { prefix, .. },
        ) => {
            let values = values
                .iter()
                .map(|value| format!("{value}{prefix}"))
                .collect::<Vec<_>>();
            let prefix = meaningful_longest_common_prefix(&values);
            if prefix.is_empty() {
                top_class_value()
            } else {
                prefix_class_value(prefix, None)
            }
        }
        (
            AbstractClassValueV0::FiniteSet { values },
            AbstractClassValueV0::Suffix { suffix, .. },
        ) => {
            let prefix = meaningful_longest_common_prefix(values);
            if prefix.is_empty() {
                suffix_class_value(suffix, None)
            } else {
                prefix_suffix_class_value(prefix, suffix, Some(suffix.len()), None)
            }
        }
        (AbstractClassValueV0::Suffix { .. }, AbstractClassValueV0::FiniteSet { values }) => {
            let suffix = meaningful_longest_common_suffix(values);
            if suffix.is_empty() {
                top_class_value()
            } else {
                suffix_class_value(suffix, None)
            }
        }
        (AbstractClassValueV0::Suffix { .. }, AbstractClassValueV0::Suffix { suffix, .. }) => {
            suffix_class_value(suffix, None)
        }
        (
            AbstractClassValueV0::Suffix { .. },
            AbstractClassValueV0::PrefixSuffix { suffix, .. },
        ) => suffix_class_value(suffix, None),
        (
            AbstractClassValueV0::PrefixSuffix { prefix, .. },
            AbstractClassValueV0::Prefix { .. },
        ) => prefix_class_value(prefix, None),
        (
            AbstractClassValueV0::PrefixSuffix {
                prefix,
                suffix,
                min_length,
                ..
            },
            AbstractClassValueV0::Exact { value },
        ) => prefix_suffix_class_value(
            prefix,
            format!("{suffix}{value}"),
            Some(min_length + value.len()),
            None,
        ),
        (
            AbstractClassValueV0::PrefixSuffix {
                prefix,
                suffix,
                min_length,
                ..
            },
            AbstractClassValueV0::FiniteSet { values },
        ) => {
            let shared_suffix = meaningful_longest_common_suffix(values);
            if shared_suffix.is_empty() {
                prefix_class_value(prefix, None)
            } else {
                prefix_suffix_class_value(
                    prefix,
                    format!("{suffix}{shared_suffix}"),
                    Some(min_length + shared_suffix.len()),
                    None,
                )
            }
        }
        (
            AbstractClassValueV0::PrefixSuffix { prefix, .. },
            AbstractClassValueV0::Suffix { suffix, .. },
        ) => prefix_suffix_class_value(prefix, suffix, Some(prefix.len() + suffix.len()), None),
        (
            AbstractClassValueV0::PrefixSuffix { prefix, .. },
            AbstractClassValueV0::PrefixSuffix {
                suffix, min_length, ..
            },
        ) => prefix_suffix_class_value(prefix, suffix, Some(prefix.len() + min_length), None),
        _ => concatenate_reduced_product_class_values(left, right).unwrap_or_else(top_class_value),
    }
}

fn intersect_non_top_class_values(
    left: &AbstractClassValueV0,
    right: &AbstractClassValueV0,
) -> AbstractClassValueV0 {
    match (
        enumerate_finite_class_values(left),
        enumerate_finite_class_values(right),
    ) {
        (Some(left_values), Some(right_values)) => {
            let right_values = right_values.into_iter().collect::<BTreeSet<_>>();
            return finite_set_class_value(
                left_values
                    .into_iter()
                    .filter(|value| right_values.contains(value)),
            );
        }
        (Some(values), None) => {
            return finite_set_class_value(
                values
                    .into_iter()
                    .filter(|value| abstract_value_matches_string(right, value)),
            );
        }
        (None, Some(values)) => {
            return finite_set_class_value(
                values
                    .into_iter()
                    .filter(|value| abstract_value_matches_string(left, value)),
            );
        }
        (None, None) => {}
    }

    intersect_reduced_product_class_values(left, right).unwrap_or_else(bottom_class_value)
}

pub fn abstract_class_value_is_subset(
    left: &AbstractClassValueV0,
    right: &AbstractClassValueV0,
) -> bool {
    if left == right {
        return true;
    }

    match (left, right) {
        (AbstractClassValueV0::Bottom, _) | (_, AbstractClassValueV0::Top) => true,
        (AbstractClassValueV0::Top, _) => false,
        _ => {
            enumerate_finite_class_values(left).is_some_and(|values| {
                values
                    .iter()
                    .all(|value| abstract_value_matches_string(right, value))
            }) || constrained_value_is_subset(left, right)
                || reduced_product_class_value_is_subset(left, right).unwrap_or(false)
        }
    }
}

fn constrained_value_is_subset(left: &AbstractClassValueV0, right: &AbstractClassValueV0) -> bool {
    match (left, right) {
        (
            AbstractClassValueV0::Prefix {
                prefix: left_prefix,
                ..
            },
            AbstractClassValueV0::Prefix {
                prefix: right_prefix,
                ..
            },
        ) => left_prefix.starts_with(right_prefix),
        (
            AbstractClassValueV0::Suffix {
                suffix: left_suffix,
                ..
            },
            AbstractClassValueV0::Suffix {
                suffix: right_suffix,
                ..
            },
        ) => left_suffix.ends_with(right_suffix),
        (
            AbstractClassValueV0::PrefixSuffix {
                prefix: left_prefix,
                suffix: _,
                ..
            },
            AbstractClassValueV0::Prefix {
                prefix: right_prefix,
                ..
            },
        ) => left_prefix.starts_with(right_prefix),
        (
            AbstractClassValueV0::PrefixSuffix {
                prefix: left_prefix,
                suffix: left_suffix,
                min_length: left_min_length,
                ..
            },
            AbstractClassValueV0::PrefixSuffix {
                prefix: right_prefix,
                suffix: right_suffix,
                min_length: right_min_length,
                ..
            },
        ) => {
            left_prefix.starts_with(right_prefix)
                && left_suffix.ends_with(right_suffix)
                && left_min_length >= right_min_length
        }
        (
            AbstractClassValueV0::PrefixSuffix {
                suffix: left_suffix,
                ..
            },
            AbstractClassValueV0::Suffix {
                suffix: right_suffix,
                ..
            },
        ) => left_suffix.ends_with(right_suffix),
        _ => false,
    }
}
