use std::collections::HashMap;
use std::collections::HashSet;

use crossterm::event::KeyCode;
use crossterm::event::KeyModifiers;
use toml::Table;
use toml::Value;

use crate::Action;
use crate::Bindings;
use crate::GlobalAction;
use crate::KeyBind;
use crate::KeyParseError;
use crate::KeySequence;
use crate::KeymapError;

/// Walk `[scope_name]` in the parsed TOML table (if present) and
/// override matching actions in `defaults`. Returns the overlaid
/// `Bindings` (replace-per-action semantics: TOML keys for an action
/// fully replace the action's defaults).
///
/// `unknown` controls how an action key with no matching variant is
/// handled: `None` returns [`KeymapError::UnknownAction`] (strict);
/// `Some(sink)` pushes a human-readable warning into `sink` and skips
/// the entry (lenient — see [`KeymapBuilder::ignore_unknown_entries`]).
pub(super) fn apply_toml_overlay<A>(
    scope_name: &str,
    defaults: Bindings<A>,
    table: Option<&Table>,
    unknown: Option<&mut Vec<String>>,
) -> Result<Bindings<A>, KeymapError>
where
    A: Action,
{
    apply_toml_overlay_with_peer(scope_name, defaults, table, None, unknown, false)
}

/// Same as [`apply_toml_overlay`], but unknown actions present in
/// `peer_action_keys` are treated as belonging to another enum that
/// shares the same TOML table. Used for the split `[global]` table.
///
/// `never_unbind` controls the empty-value case: when `true` (the
/// navigation scope), an empty TOML value (e.g. a stale `home = ""`)
/// keeps the compiled default instead of clearing it, so a navigation
/// action can never end up with no key. When `false` (pane / global
/// scopes), an empty value clears the action's binding as before.
pub(super) fn apply_toml_overlay_with_peer<A>(
    scope_name: &str,
    mut defaults: Bindings<A>,
    table: Option<&Table>,
    peer_action_keys: Option<&HashSet<&'static str>>,
    mut unknown: Option<&mut Vec<String>>,
    never_unbind: bool,
) -> Result<Bindings<A>, KeymapError>
where
    A: Action,
{
    let Some(table) = table else {
        return Ok(defaults);
    };
    let Some(scope_value) = table.get(scope_name) else {
        return Ok(defaults);
    };
    let Some(scope_table) = scope_value.as_table() else {
        return Ok(defaults);
    };

    for (action_key, value) in scope_table {
        let Some(action) = A::from_toml_key(action_key) else {
            if peer_action_keys.is_some_and(|keys| keys.contains(action_key.as_str())) {
                continue;
            }
            if let Some(sink) = unknown.as_deref_mut() {
                sink.push(format!(
                    "unknown action '{action_key}' in [{scope_name}] (ignored)"
                ));
                continue;
            }
            return Err(KeymapError::UnknownAction {
                scope:  scope_name.to_string(),
                action: action_key.clone(),
            });
        };
        let keys = parse_toml_value(scope_name, action_key, value)?;
        if never_unbind && keys.is_empty() {
            // A stale `home = ""` (or any empty navigation value) keeps
            // the compiled default rather than unbinding the action. No
            // warning: the entry names a real action, it just resolves
            // to "leave the default in place."
            continue;
        }
        check_in_array_duplicates(scope_name, action_key, &keys)?;
        defaults.override_action(&action, keys);
    }

    check_cross_action_collision(scope_name, &defaults)?;
    Ok(defaults)
}

pub(super) fn action_key_set<A: Action>() -> HashSet<&'static str> {
    A::ALL.iter().map(|action| action.toml_key()).collect()
}

pub(super) fn framework_global_action_key_set() -> HashSet<&'static str> {
    let mut keys = action_key_set::<GlobalAction>();
    keys.insert("settings");
    keys
}

/// Parse a TOML scope entry value into `Vec<KeySequence>`. Accepts a
/// single string or an array of strings.
fn parse_toml_value(
    scope: &str,
    action: &str,
    value: &Value,
) -> Result<Vec<KeySequence>, KeymapError> {
    match value {
        Value::String(s) => {
            if s.is_empty() {
                return Ok(Vec::new());
            }
            let bind =
                canonicalize_toml_bind_sequence(&KeySequence::parse(s).map_err(|source| {
                    KeymapError::InvalidBinding {
                        scope: scope.to_string(),
                        action: action.to_string(),
                        source,
                    }
                })?);
            Ok(vec![bind])
        },
        Value::Array(items) => {
            let mut out = Vec::with_capacity(items.len());
            for item in items {
                let s = item.as_str().ok_or_else(|| KeymapError::InvalidBinding {
                    scope:  scope.to_string(),
                    action: action.to_string(),
                    source: KeyParseError::Empty,
                })?;
                let bind = KeySequence::parse(s).map_err(|source| KeymapError::InvalidBinding {
                    scope: scope.to_string(),
                    action: action.to_string(),
                    source,
                })?;
                let bind = canonicalize_toml_bind_sequence(&bind);
                out.push(bind);
            }
            Ok(out)
        },
        _ => Err(KeymapError::InvalidBinding {
            scope:  scope.to_string(),
            action: action.to_string(),
            source: KeyParseError::Empty,
        }),
    }
}

fn canonicalize_toml_bind_sequence(sequence: &KeySequence) -> KeySequence {
    KeySequence::new(
        sequence
            .keys()
            .iter()
            .copied()
            .map(canonicalize_toml_bind)
            .collect(),
    )
}

fn canonicalize_toml_bind(mut key_bind: KeyBind) -> KeyBind {
    key_bind.code = match key_bind.code {
        KeyCode::Char('+') => KeyCode::Char('='),
        other => other,
    };
    if matches!(key_bind.code, KeyCode::Char('=')) {
        key_bind.mods &= !KeyModifiers::SHIFT;
    }
    key_bind
}

/// Reject duplicate keys inside a single TOML array. Surfaces
/// [`KeymapError::InArrayDuplicate`].
fn check_in_array_duplicates(
    scope: &str,
    action: &str,
    keys: &[KeySequence],
) -> Result<(), KeymapError> {
    for (i, key) in keys.iter().enumerate() {
        if keys[..i].iter().any(|k| k == key) {
            return Err(KeymapError::InArrayDuplicate {
                scope:  scope.to_string(),
                action: action.to_string(),
                key:    key.display(),
            });
        }
    }
    Ok(())
}

/// Reject two actions in the same scope sharing one key. Surfaces
/// [`KeymapError::CrossActionCollision`].
pub(super) fn check_cross_action_collision<A: Action>(
    scope: &str,
    bindings: &Bindings<A>,
) -> Result<(), KeymapError> {
    let mut seen: HashMap<KeySequence, A> = HashMap::new();
    for (key, action) in bindings_entries(bindings) {
        if let Some(existing) = seen.get(key)
            && *existing != *action
        {
            return Err(KeymapError::CrossActionCollision {
                scope:   scope.to_string(),
                key:     key.display(),
                actions: (
                    existing.toml_key().to_string(),
                    action.toml_key().to_string(),
                ),
            });
        }
        seen.insert(key.clone(), *action);
    }
    let entries = bindings_entries(bindings).collect::<Vec<_>>();
    for (idx, (left_key, left_action)) in entries.iter().enumerate() {
        for (right_key, right_action) in entries.iter().skip(idx + 1) {
            if left_key.starts_with_strict(right_key.keys())
                || right_key.starts_with_strict(left_key.keys())
            {
                return Err(KeymapError::CrossActionCollision {
                    scope:   scope.to_string(),
                    key:     format!("{} / {}", left_key.display(), right_key.display()),
                    actions: (
                        left_action.toml_key().to_string(),
                        right_action.toml_key().to_string(),
                    ),
                });
            }
        }
    }
    Ok(())
}

/// Borrow the entries of a [`Bindings`] in insertion order.
fn bindings_entries<A>(bindings: &Bindings<A>) -> impl Iterator<Item = (&KeySequence, &A)> {
    bindings.entries().iter().map(|(k, a)| (k, a))
}
