use std::collections::{HashMap, HashSet};
use std::ffi::OsString;
use std::time::{Duration, Instant};

use clap::Command;

use crate::editor_state::EditorState;
use crate::frame_snapshot::FrameSnapshot;
use crate::query::selectors as selector_query;
use crate::spec::{ArgSpec, CommandPath, CommandSpec, SelectionError};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Focus {
    Sidebar,
    Form,
    Search,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActiveTab {
    Inputs,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgValue {
    Bool(bool),
    Text(String),
    Choice(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputSource {
    User,
    Default,
    Env,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputValueOccurrence {
    pub values: Vec<String>,
    pub source: InputSource,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArgInput {
    Flag {
        present: bool,
        source: InputSource,
    },
    Count {
        occurrences: usize,
        source: InputSource,
    },
    Values {
        occurrences: Vec<InputValueOccurrence>,
    },
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArgInputState {
    pub value: ArgInput,
    pub touched: bool,
}

#[derive(Debug, Default, Clone)]
pub struct CommandFormState {
    pub inputs: HashMap<String, ArgInputState>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HoverTarget {
    Run,
    Exit,
    Search,
    Focus,
    Help,
    Preview,
    FooterStatus,
}

#[derive(Debug, Clone)]
pub struct MouseSelection {
    pub arg_id: String,
    pub anchor_row: u16,
    pub anchor_col: u16,
    pub active: bool,
}

#[derive(Debug, Clone)]
pub struct Toast {
    pub message: String,
    pub expires_at: Instant,
    pub is_error: bool,
}

#[derive(Debug)]
pub struct DomainState {
    pub root: CommandSpec,
    pub validation_command: Option<Command>,
    pub selected_path: CommandPath,
    pub expanded: HashSet<String>,
    pub forms: HashMap<String, CommandFormState>,
    derived_revision: u64,
}

#[derive(Debug)]
pub struct UiState {
    pub focus: Focus,
    pub active_tab: ActiveTab,
    pub last_non_help_tab: ActiveTab,
    pub help_open: bool,
    pub help_scroll: u16,
    pub selected_arg_index: usize,
    pub search_query: String,
    pub editors: EditorState,
    pub dropdown_open: Option<String>,
    pub dropdown_scroll: usize,
    pub dropdown_cursor: usize,
    pub sidebar_scroll: usize,
    pub form_scroll: u16,
    pub hover: Option<HoverTarget>,
    pub hover_tab: Option<ActiveTab>,
    pub mouse_select: Option<MouseSelection>,
}

#[derive(Debug, Default)]
pub struct NotificationState {
    pub toast: Option<Toast>,
}

#[derive(Debug)]
pub struct AppState {
    pub domain: DomainState,
    pub ui: UiState,
    pub notifications: NotificationState,
    derived_cache: Option<DerivedCache>,
}

#[derive(Debug, Clone)]
struct DerivedCache {
    revision: u64,
    derived: crate::pipeline::DerivedState,
}

impl DomainState {
    #[allow(dead_code)]
    pub fn new(root: CommandSpec) -> Self {
        Self::new_with_command(root, None)
    }

    pub fn new_with_command(root: CommandSpec, validation_command: Option<Command>) -> Self {
        let mut expanded = HashSet::new();
        expanded.insert(root.name.clone());
        Self {
            root,
            validation_command,
            selected_path: CommandPath::default(),
            expanded,
            forms: HashMap::new(),
            derived_revision: 0,
        }
    }

    pub fn selected_path(&self) -> &CommandPath {
        &self.selected_path
    }

    pub fn current_command(&self) -> &CommandSpec {
        self.domain_resolved_command().command
    }

    fn domain_resolved_command(&self) -> crate::spec::ResolvedCommand<'_> {
        self.root.resolved(&self.selected_path)
    }

    #[allow(dead_code)]
    pub fn command_path_key(&self) -> String {
        self.command_path_key_for(&self.selected_path)
    }

    pub fn command_path_key_for(&self, path: &CommandPath) -> String {
        path.to_key(&self.root.name)
    }

    pub fn current_form(&self) -> Option<CommandFormState> {
        self.effective_form_for_path(&self.selected_path)
    }

    pub fn initialize_current_form_defaults(&mut self) {
        let mut changed = false;
        let args = self
            .root
            .args_defined_on_path(&self.selected_path)
            .unwrap_or_default()
            .into_iter()
            .map(|(owner_path, arg)| (owner_path.clone(), arg.clone()))
            .collect::<Vec<_>>();

        for (owner_path, arg) in args {
            let key = self.command_path_key_for(&owner_path);
            let default_input = Self::initial_input_state(&arg);
            let should_remove = {
                let form = self.forms.entry(key.clone()).or_default();
                if let Some(input) = default_input
                    && !form.inputs.contains_key(&arg.id)
                {
                    form.inputs.insert(arg.id.clone(), input);
                    changed = true;
                }
                form.is_empty()
            };
            if should_remove && self.forms.remove(&key).is_some() {
                changed = true;
            }
        }
        if changed {
            self.bump_derived_revision();
        }
    }

    pub fn set_text_value(&mut self, arg_id: &str, text: &str) {
        let Some(arg) = self.arg_for_input(arg_id).cloned() else {
            return;
        };
        self.replace_occurrences(arg_id, text_value_occurrences(&arg, text));
    }

    pub fn set_choice_value(&mut self, arg_id: &str, value: String) {
        self.replace_occurrences(
            arg_id,
            vec![InputValueOccurrence {
                values: vec![value],
                source: InputSource::User,
            }],
        );
    }

    pub fn toggle_flag(&mut self, arg_id: &str) {
        if !self
            .arg_for_input(arg_id)
            .is_some_and(crate::spec::ArgSpec::uses_toggle_semantics)
        {
            return;
        }
        let current = self
            .effective_arg_input(arg_id)
            .and_then(|input| match input.value {
                ArgInput::Flag { present, .. } => Some(present),
                _ => None,
            })
            .unwrap_or(false);
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            input.value = ArgInput::Flag {
                present: !current,
                source: InputSource::User,
            };
        });
    }

    pub fn mark_touched(&mut self, arg_id: &str) {
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            input.touched = true;
        });
    }

    pub fn toggle_flag_touched(&mut self, arg_id: &str) {
        self.toggle_flag(arg_id);
        self.mark_touched(arg_id);
    }

    pub fn set_choice_value_touched(&mut self, arg_id: &str, value: String) {
        self.set_choice_value(arg_id, value);
        self.mark_touched(arg_id);
    }

    pub fn toggle_choice_value_touched(&mut self, arg_id: &str, value: &str) {
        let Some(arg) = self.arg_for_input(arg_id).cloned() else {
            return;
        };
        let mut values = self
            .current_form()
            .map_or_else(Vec::new, |inputs| inputs.selected_values(&arg));
        if let Some(index) = values.iter().position(|selected| selected == value) {
            values.remove(index);
        } else {
            values.push(value.to_string());
        }
        self.replace_values(arg_id, values);
        self.mark_touched(arg_id);
    }

    pub fn clear_value_and_untouch(&mut self, arg_id: &str) {
        let Some(arg) = self.arg_for_input(arg_id).cloned() else {
            return;
        };
        let Some(owner_key) = self.owner_key_for_arg(arg_id) else {
            return;
        };
        let default_input = Self::initial_input_state(&arg);
        let mut changed = false;
        let should_remove = {
            let form = self.forms.entry(owner_key.clone()).or_default();
            match default_input {
                Some(input) => {
                    if form.inputs.get(arg_id) != Some(&input) {
                        form.inputs.insert(arg_id.to_string(), input);
                        changed = true;
                    }
                }
                None => {
                    if form.inputs.remove(arg_id).is_some() {
                        changed = true;
                    }
                }
            }
            form.is_empty()
        };
        if should_remove && self.forms.remove(&owner_key).is_some() {
            changed = true;
        }
        if changed {
            self.bump_derived_revision();
        }
    }

    pub fn is_touched(&self, arg_id: &str) -> bool {
        self.current_form()
            .is_some_and(|form| form.is_touched(arg_id))
    }

    pub fn select_command_path(&mut self, path: &[String]) -> Result<(), SelectionError> {
        let normalized = self
            .root
            .normalize_path(path)
            .ok_or(SelectionError::UnknownPath)?;
        self.selected_path = normalized.clone();
        let prefix_path_len = normalized.as_slice().len().saturating_sub(1);
        let prefix_path = CommandPath::new(normalized.as_slice()[..prefix_path_len].to_vec());
        for key in self.root.expand_prefix_keys(&prefix_path) {
            self.expanded.insert(key);
        }
        self.bump_derived_revision();
        Ok(())
    }

    pub fn select_command_by_search_path(&mut self, start: &str) -> Result<(), SelectionError> {
        let normalized = self
            .root
            .find_path_by_search_path(start)
            .ok_or(SelectionError::UnknownPath)?;
        self.select_command_path(normalized.as_slice())
    }

    pub fn replace_values(&mut self, arg_id: &str, values: Vec<String>) {
        self.with_arg_input_state_mut(arg_id, move |arg, input| {
            *input = ArgInputState::from_values(arg, values, InputSource::User, true);
        });
    }

    pub fn replace_occurrences(&mut self, arg_id: &str, occurrences: Vec<InputValueOccurrence>) {
        self.with_arg_input_state_mut(arg_id, move |_arg, input| {
            *input = ArgInputState {
                value: ArgInput::Values { occurrences },
                touched: true,
            };
        });
    }

    #[allow(dead_code)]
    pub fn append_value(&mut self, arg_id: &str, value: String) {
        self.with_arg_input_state_mut(arg_id, move |arg, input| match &mut input.value {
            ArgInput::Values { occurrences } => {
                occurrences.push(InputValueOccurrence {
                    values: vec![value],
                    source: InputSource::User,
                });
                input.touched = true;
            }
            _ => {
                *input = ArgInputState::from_values(arg, vec![value], InputSource::User, true);
            }
        });
    }

    #[allow(dead_code)]
    pub fn remove_value(&mut self, arg_id: &str, occurrence_index: usize, value_index: usize) {
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            if let ArgInput::Values { occurrences } = &mut input.value {
                if let Some(occurrence) = occurrences.get_mut(occurrence_index)
                    && value_index < occurrence.values.len()
                {
                    occurrence.values.remove(value_index);
                }
                occurrences.retain(|occurrence| !occurrence.values.is_empty());
                input.touched = true;
            }
        });
    }

    #[allow(dead_code)]
    pub fn reorder_occurrence(&mut self, arg_id: &str, from: usize, to: usize) {
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            if let ArgInput::Values { occurrences } = &mut input.value {
                if from >= occurrences.len() || to >= occurrences.len() || from == to {
                    return;
                }
                let occurrence = occurrences.remove(from);
                occurrences.insert(to, occurrence);
                input.touched = true;
            }
        });
    }

    #[allow(dead_code)]
    pub fn increment_counter(&mut self, arg_id: &str) {
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            let current = match input.value {
                ArgInput::Count { occurrences, .. } => occurrences,
                _ => 0,
            };
            input.value = ArgInput::Count {
                occurrences: current.saturating_add(1),
                source: InputSource::User,
            };
            input.touched = true;
        });
    }

    #[allow(dead_code)]
    pub fn decrement_counter(&mut self, arg_id: &str) {
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            let current = match input.value {
                ArgInput::Count { occurrences, .. } => occurrences,
                _ => 0,
            };
            input.value = ArgInput::Count {
                occurrences: current.saturating_sub(1),
                source: InputSource::User,
            };
            input.touched = true;
        });
    }

    #[allow(dead_code)]
    pub fn toggle_optional_value_flag(&mut self, arg_id: &str, enabled: bool) {
        self.with_arg_input_state_mut(arg_id, |_arg, input| {
            input.value = ArgInput::Flag {
                present: enabled,
                source: InputSource::User,
            };
            input.touched = true;
        });
    }

    #[allow(dead_code)]
    pub fn resolve_invocation_state(&self) -> Vec<(CommandPath, CommandFormState)> {
        self.root
            .command_lineage(&self.selected_path)
            .unwrap_or_default()
            .into_iter()
            .map(|command| {
                let key = self.command_path_key_for(&command.path);
                (
                    command.path.clone(),
                    self.forms.get(&key).cloned().unwrap_or_default(),
                )
            })
            .collect()
    }

    fn effective_form_for_path(&self, path: &CommandPath) -> Option<CommandFormState> {
        let mut form = CommandFormState::default();

        for (_, arg) in self
            .root
            .effective_args_for_path(path)?
            .into_iter()
            .filter(|(_, arg)| !arg.is_external_subcommand_field() || arg.owner_path() == path)
        {
            let input = self.stored_arg_input(arg);
            if let Some(input) = input {
                form.inputs.insert(arg.id.clone(), input);
            }
        }

        (!form.is_empty()).then_some(form)
    }

    pub(crate) fn arg_for_input(&self, arg_id: &str) -> Option<&ArgSpec> {
        self.root
            .effective_args_for_path(&self.selected_path)?
            .into_iter()
            .map(|(_, arg)| arg)
            .filter(|arg| {
                !arg.is_external_subcommand_field() || arg.owner_path() == &self.selected_path
            })
            .find(|arg| arg.id == arg_id && !arg.is_help_action())
    }

    fn owner_key_for_arg(&self, arg_id: &str) -> Option<String> {
        let owner_path = self
            .arg_for_input(arg_id)
            .map(|arg| arg.owner_path().clone())?;
        Some(self.command_path_key_for(&owner_path))
    }

    fn effective_arg_input(&self, arg_id: &str) -> Option<ArgInputState> {
        let arg = self.arg_for_input(arg_id)?;
        self.stored_arg_input(arg)
    }

    fn stored_arg_input(&self, arg: &ArgSpec) -> Option<ArgInputState> {
        let key = self.command_path_key_for(arg.owner_path());
        self.forms
            .get(&key)
            .and_then(|form| form.inputs.get(&arg.id))
            .cloned()
    }

    fn with_arg_input_state_mut<F>(&mut self, arg_id: &str, mutate: F)
    where
        F: FnOnce(&ArgSpec, &mut ArgInputState),
    {
        let Some(arg) = self.arg_for_input(arg_id).cloned() else {
            return;
        };
        let key = self.command_path_key_for(arg.owner_path());
        let default_input = Self::initial_input_state(&arg);
        let mut changed = false;
        let should_remove = {
            let form = self.forms.entry(key.clone()).or_default();
            let mut input = form
                .inputs
                .get(arg_id)
                .cloned()
                .or_else(|| default_input.clone())
                .unwrap_or_else(|| ArgInputState::empty_for(&arg));
            mutate(&arg, &mut input);
            if input.is_effectively_empty() {
                if form.inputs.remove(arg_id).is_some() {
                    changed = true;
                }
            } else if form.inputs.get(arg_id) != Some(&input) {
                form.inputs.insert(arg_id.to_string(), input);
                changed = true;
            }
            form.is_empty()
        };
        if should_remove && self.forms.remove(&key).is_some() {
            changed = true;
        }
        if changed {
            self.bump_derived_revision();
        }
    }

    pub(crate) fn derived_revision(&self) -> u64 {
        self.derived_revision
    }

    fn bump_derived_revision(&mut self) {
        self.derived_revision = self.derived_revision.saturating_add(1);
    }

    fn initial_input_state(arg: &ArgSpec) -> Option<ArgInputState> {
        if matches!(arg.action_kind(), crate::spec::ArgActionKind::Count) {
            return Some(ArgInputState {
                value: ArgInput::Count {
                    occurrences: 0,
                    source: InputSource::Default,
                },
                touched: false,
            });
        }

        if arg.uses_toggle_semantics() {
            return Some(ArgInputState {
                value: ArgInput::Flag {
                    present: false,
                    source: InputSource::Default,
                },
                touched: false,
            });
        }

        if let Some(env) = arg.metadata.defaults.env.as_deref()
            && let Ok(value) = std::env::var(env)
        {
            return Some(ArgInputState::from_values(
                arg,
                vec![value],
                InputSource::Env,
                false,
            ));
        }

        if !arg.default_values.is_empty() {
            return Some(ArgInputState::from_values(
                arg,
                arg.default_values.clone(),
                InputSource::Default,
                false,
            ));
        }

        None
    }
}

impl CommandFormState {
    pub fn input(&self, arg_id: &str) -> Option<&ArgInputState> {
        self.inputs.get(arg_id)
    }

    pub fn compatibility_value(&self, arg: &ArgSpec) -> Option<ArgValue> {
        self.input(&arg.id)
            .and_then(|input| input.compatibility_value(arg))
    }

    pub fn is_touched(&self, arg_id: &str) -> bool {
        self.input(arg_id).is_some_and(|input| input.touched)
    }

    pub fn is_empty(&self) -> bool {
        self.inputs.is_empty()
    }

    pub fn selected_values(&self, arg: &ArgSpec) -> Vec<String> {
        self.input(&arg.id)
            .map_or_else(Vec::new, ArgInputState::selected_values)
    }

    pub fn input_source(&self, arg_id: &str) -> Option<InputSource> {
        self.input(arg_id).and_then(ArgInputState::input_source)
    }
}

impl ArgInput {
    fn values_from_arg(arg: &ArgSpec, values: Vec<String>, source: InputSource) -> Self {
        let occurrences = if arg.allows_multiple_occurrences() {
            values
                .into_iter()
                .map(|value| InputValueOccurrence {
                    values: vec![value],
                    source,
                })
                .collect()
        } else {
            vec![InputValueOccurrence { values, source }]
        };
        Self::Values { occurrences }
    }
}

impl ArgInputState {
    fn from_values(arg: &ArgSpec, values: Vec<String>, source: InputSource, touched: bool) -> Self {
        Self {
            value: ArgInput::values_from_arg(arg, values, source),
            touched,
        }
    }

    fn empty_for(arg: &ArgSpec) -> Self {
        if arg.uses_toggle_semantics() {
            Self {
                value: ArgInput::Flag {
                    present: false,
                    source: InputSource::Default,
                },
                touched: false,
            }
        } else {
            Self {
                value: ArgInput::Values {
                    occurrences: Vec::new(),
                },
                touched: false,
            }
        }
    }

    pub fn compatibility_value(&self, arg: &ArgSpec) -> Option<ArgValue> {
        match &self.value {
            ArgInput::Flag { present, .. } => Some(ArgValue::Bool(*present)),
            ArgInput::Count { occurrences, .. } => Some(ArgValue::Text(occurrences.to_string())),
            ArgInput::Values { occurrences } => {
                let values = self.selected_values();
                if values.is_empty() {
                    return None;
                }
                if arg.uses_optional_value_semantics() {
                    let rendered = occurrences
                        .iter()
                        .map(|occurrence| render_occurrence_text(arg, &occurrence.values))
                        .filter(|value| !value.is_empty())
                        .collect::<Vec<_>>()
                        .join("\n");
                    return Some(ArgValue::Text(rendered));
                }
                if arg.has_value_choices() && !arg.is_multi_value_input() {
                    values.first().cloned().map(ArgValue::Choice)
                } else {
                    let rendered = occurrences
                        .iter()
                        .map(|occurrence| render_occurrence_text(arg, &occurrence.values))
                        .filter(|value| !value.is_empty())
                        .collect::<Vec<_>>()
                        .join("\n");
                    Some(ArgValue::Text(rendered))
                }
            }
        }
    }

    pub fn selected_values(&self) -> Vec<String> {
        match &self.value {
            ArgInput::Flag { present, .. } => {
                present.then(|| "true".to_string()).into_iter().collect()
            }
            ArgInput::Count { occurrences, .. } => (*occurrences > 0)
                .then(|| occurrences.to_string())
                .into_iter()
                .collect(),
            ArgInput::Values { occurrences } => occurrences
                .iter()
                .flat_map(|occurrence| occurrence.values.iter())
                .filter(|value| !value.is_empty())
                .cloned()
                .collect(),
        }
    }

    pub fn input_source(&self) -> Option<InputSource> {
        match &self.value {
            ArgInput::Flag { source, .. } | ArgInput::Count { source, .. } => Some(*source),
            ArgInput::Values { occurrences } => occurrences
                .iter()
                .find(|occurrence| occurrence.values.iter().any(|value| !value.is_empty()))
                .map(|occurrence| occurrence.source),
        }
    }

    pub fn is_effectively_empty(&self) -> bool {
        match &self.value {
            ArgInput::Flag { present, source } => {
                !present && !self.touched && *source == InputSource::Default
            }
            ArgInput::Count {
                occurrences,
                source,
            } => *occurrences == 0 && !self.touched && *source == InputSource::Default,
            ArgInput::Values { occurrences } => occurrences.is_empty(),
        }
    }
}

fn text_value_occurrences(arg: &ArgSpec, text: &str) -> Vec<InputValueOccurrence> {
    if arg.allows_multiple_occurrences() {
        return text
            .lines()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(|value| InputValueOccurrence {
                values: split_occurrence_values(arg, value),
                source: InputSource::User,
            })
            .collect();
    }

    vec![InputValueOccurrence {
        values: split_occurrence_values(arg, text),
        source: InputSource::User,
    }]
}

pub(crate) fn split_occurrence_values(arg: &ArgSpec, text: &str) -> Vec<String> {
    if arg.accepts_multiple_values_per_occurrence() {
        if let Some(delimiter) = arg.metadata.syntax.value_delimiter {
            return text
                .split(delimiter)
                .map(str::trim)
                .filter(|value| !value.is_empty())
                .map(str::to_string)
                .collect();
        }

        return text
            .lines()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(str::to_string)
            .collect();
    }

    vec![text.to_string()]
}

pub(crate) fn render_occurrence_text(arg: &ArgSpec, values: &[String]) -> String {
    if let Some(delimiter) = arg.metadata.syntax.value_delimiter {
        values.join(&delimiter.to_string())
    } else if arg.accepts_multiple_values_per_occurrence() {
        values.join("\n")
    } else {
        values.first().cloned().unwrap_or_default()
    }
}

impl UiState {
    pub fn visible_tabs() -> [ActiveTab; 1] {
        [ActiveTab::Inputs]
    }

    pub fn focus_first_tab(&mut self, visible_args: &[(usize, &ArgSpec)]) {
        self.active_tab = Self::visible_tabs()[0];
        self.last_non_help_tab = self.active_tab;
        self.ensure_selected_arg_visible(visible_args);
        self.reset_transient_form_ui();
    }

    pub fn ensure_active_tab_visible(&mut self, visible_args: &[(usize, &ArgSpec)]) {
        if Self::visible_tabs().contains(&self.active_tab) {
            return;
        }

        self.active_tab = Self::visible_tabs()[0];
        self.last_non_help_tab = self.active_tab;
        self.ensure_selected_arg_visible(visible_args);
        self.reset_transient_form_ui();
    }

    pub fn ensure_selected_arg_visible(&mut self, visible_args: &[(usize, &ArgSpec)]) {
        if visible_args.is_empty() {
            self.selected_arg_index = 0;
            return;
        }
        if !visible_args
            .iter()
            .any(|(index, _)| *index == self.selected_arg_index)
        {
            self.selected_arg_index = visible_args[0].0;
        }
    }

    pub fn form_scroll(&self, frame_snapshot: &FrameSnapshot) -> u16 {
        frame_snapshot.form_scroll(self.form_scroll)
    }

    pub fn clamp_form_scroll(&mut self, frame_snapshot: &FrameSnapshot) {
        self.form_scroll = self.form_scroll(frame_snapshot);
    }

    pub fn help_scroll(&self, frame_snapshot: &FrameSnapshot) -> u16 {
        frame_snapshot.help_scroll(self.help_scroll)
    }

    pub fn clamp_help_scroll(&mut self, frame_snapshot: &FrameSnapshot) {
        self.help_scroll = self.help_scroll(frame_snapshot);
    }

    pub fn focus_sidebar(&mut self) {
        self.dismiss_transient_interaction();
        self.focus = Focus::Sidebar;
    }

    pub fn focus_form(&mut self) {
        self.focus = Focus::Form;
    }

    pub fn focus_search(&mut self) {
        self.dismiss_transient_interaction();
        self.focus = Focus::Search;
    }

    pub fn focus_next(&mut self) {
        self.dismiss_transient_interaction();
        self.focus = match self.focus {
            Focus::Sidebar => Focus::Search,
            Focus::Search => Focus::Form,
            Focus::Form => Focus::Sidebar,
        };
    }

    pub fn focus_previous(&mut self) {
        self.dismiss_transient_interaction();
        self.focus = match self.focus {
            Focus::Sidebar => Focus::Form,
            Focus::Search => Focus::Sidebar,
            Focus::Form => Focus::Search,
        };
    }

    pub fn toggle_help(&mut self) {
        self.help_open = !self.help_open;
        self.help_scroll = 0;
        self.close_dropdown();
        self.clear_mouse_selection();
    }

    pub fn close_dropdown(&mut self) {
        self.dropdown_open = None;
        self.dropdown_cursor = 0;
    }

    pub fn open_dropdown(&mut self, arg_id: impl Into<String>, scroll: usize, cursor: usize) {
        self.dropdown_open = Some(arg_id.into());
        self.dropdown_scroll = scroll;
        self.dropdown_cursor = cursor;
    }

    pub fn set_dropdown_scroll(&mut self, scroll: usize) {
        self.dropdown_scroll = scroll;
    }

    pub fn set_dropdown_cursor(&mut self, cursor: usize) {
        self.dropdown_cursor = cursor;
    }

    pub fn dropdown_scroll(&self, total_rows: usize, visible_rows: usize) -> usize {
        clamp_dropdown_scroll(self.dropdown_scroll, total_rows, visible_rows)
    }

    pub fn dropdown_cursor(&self, total_rows: usize) -> usize {
        if total_rows == 0 {
            0
        } else {
            self.dropdown_cursor.min(total_rows - 1)
        }
    }

    pub fn clamp_dropdown_scroll(&mut self, total_rows: usize, visible_rows: usize) {
        self.dropdown_scroll = self.dropdown_scroll(total_rows, visible_rows);
    }

    pub fn adjust_dropdown_scroll(&mut self, delta: i16, total_rows: usize, visible_rows: usize) {
        if delta.is_negative() {
            self.dropdown_scroll = self
                .dropdown_scroll
                .saturating_sub(usize::from(delta.unsigned_abs()));
        } else {
            self.dropdown_scroll = self
                .dropdown_scroll
                .saturating_add(usize::from(delta.unsigned_abs()));
        }
        self.clamp_dropdown_scroll(total_rows, visible_rows);
    }

    pub fn set_form_scroll(&mut self, scroll: u16) {
        self.form_scroll = scroll;
    }

    pub fn sidebar_scroll(&self, total_rows: usize, visible_rows: usize) -> usize {
        clamp_dropdown_scroll(self.sidebar_scroll, total_rows, visible_rows)
    }

    pub fn set_sidebar_scroll(&mut self, scroll: usize) {
        self.sidebar_scroll = scroll;
    }

    pub fn clamp_sidebar_scroll(&mut self, total_rows: usize, visible_rows: usize) {
        self.sidebar_scroll = self.sidebar_scroll(total_rows, visible_rows);
    }

    pub fn adjust_sidebar_scroll(&mut self, delta: i16, total_rows: usize, visible_rows: usize) {
        if delta.is_negative() {
            self.sidebar_scroll = self
                .sidebar_scroll
                .saturating_sub(usize::from(delta.unsigned_abs()));
        } else {
            self.sidebar_scroll = self
                .sidebar_scroll
                .saturating_add(usize::from(delta.unsigned_abs()));
        }
        self.clamp_sidebar_scroll(total_rows, visible_rows);
    }

    pub fn adjust_form_scroll(&mut self, delta: i16) {
        if delta.is_negative() {
            self.form_scroll = self.form_scroll.saturating_sub(delta.unsigned_abs());
        } else {
            self.form_scroll = self.form_scroll.saturating_add(delta.unsigned_abs());
        }
    }

    pub fn adjust_help_scroll(&mut self, delta: i16) {
        if delta.is_negative() {
            self.help_scroll = self.help_scroll.saturating_sub(delta.unsigned_abs());
        } else {
            self.help_scroll = self.help_scroll.saturating_add(delta.unsigned_abs());
        }
    }

    pub fn set_selected_arg_index(&mut self, selected_arg_index: usize) {
        self.selected_arg_index = selected_arg_index;
    }

    pub fn set_mouse_selection(&mut self, mouse_select: Option<MouseSelection>) {
        self.mouse_select = mouse_select;
    }

    pub fn clear_mouse_selection(&mut self) {
        self.mouse_select = None;
    }

    pub fn dismiss_transient_interaction(&mut self) {
        self.close_dropdown();
        self.clear_mouse_selection();
    }

    pub fn reset_transient_form_ui(&mut self) {
        self.form_scroll = 0;
        self.dismiss_transient_interaction();
    }

    pub fn set_hover(&mut self, hover: Option<HoverTarget>) {
        self.hover = hover;
    }

    pub fn set_hover_tab(&mut self, hover_tab: Option<ActiveTab>) {
        self.hover_tab = hover_tab;
    }
}

impl NotificationState {
    pub fn show_toast(&mut self, message: impl Into<String>, duration: Duration, is_error: bool) {
        self.toast = Some(Toast {
            message: message.into(),
            expires_at: Instant::now() + duration,
            is_error,
        });
    }

    pub fn clear_expired_toast(&mut self) {
        if self
            .toast
            .as_ref()
            .is_some_and(|toast| Instant::now() >= toast.expires_at)
        {
            self.toast = None;
        }
    }
}

impl AppState {
    #[allow(dead_code)]
    pub fn new(root: CommandSpec) -> Self {
        Self::new_with_command(root, None)
    }

    pub fn from_command(command: &Command) -> Self {
        Self::new_with_command(CommandSpec::from_command(command), Some(command.clone()))
    }

    pub fn new_with_command(root: CommandSpec, validation_command: Option<Command>) -> Self {
        let mut state = Self {
            domain: DomainState::new_with_command(root, validation_command),
            ui: UiState {
                focus: Focus::Sidebar,
                active_tab: ActiveTab::Inputs,
                last_non_help_tab: ActiveTab::Inputs,
                help_open: false,
                help_scroll: 0,
                selected_arg_index: 0,
                search_query: String::new(),
                editors: EditorState::default(),
                dropdown_open: None,
                dropdown_scroll: 0,
                dropdown_cursor: 0,
                sidebar_scroll: 0,
                form_scroll: 0,
                hover: None,
                hover_tab: None,
                mouse_select: None,
            },
            notifications: NotificationState::default(),
            derived_cache: None,
        };
        state.initialize_current_command();
        state
    }

    pub fn initialize_current_command(&mut self) {
        self.domain.initialize_current_form_defaults();
        self.sync_visible_form_selection();
    }

    pub fn sync_visible_form_selection(&mut self) {
        let active_args = selector_query::visible_form_args(
            &self.domain.root,
            self.domain.selected_path(),
            self.ui.active_tab,
        );
        let visible = selector_query::visible_form_arg_pairs(&active_args);
        self.ui.ensure_active_tab_visible(&visible);
        self.ui.ensure_selected_arg_visible(&visible);
    }

    pub fn select_command_path(&mut self, path: &[String]) -> Result<(), SelectionError> {
        self.domain.select_command_path(path)?;
        self.initialize_current_command();
        Ok(())
    }

    pub fn select_command_by_search_path(&mut self, start: &str) -> Result<(), SelectionError> {
        self.domain.select_command_by_search_path(start)?;
        self.initialize_current_command();
        Ok(())
    }

    pub(crate) fn derived(&mut self) -> &crate::pipeline::DerivedState {
        let revision = self.domain.derived_revision();
        let needs_refresh = self
            .derived_cache
            .as_ref()
            .is_none_or(|cache| cache.revision != revision);
        if needs_refresh {
            let derived = crate::pipeline::derive(self);
            self.derived_cache = Some(DerivedCache { revision, derived });
        }
        &self
            .derived_cache
            .as_ref()
            .expect("derived cache should exist after refresh")
            .derived
    }

    pub(crate) fn authoritative_argv(&mut self) -> Vec<OsString> {
        self.derived().serialization.argv.clone()
    }

    pub(crate) fn rendered_command(&mut self) -> Option<String> {
        self.derived()
            .rendered_command
            .as_ref()
            .map(|command| command.text.clone())
    }

    pub(crate) fn derived_validation(&mut self) -> crate::pipeline::ValidationState {
        self.derived().validation.clone()
    }

    pub(crate) fn field_semantics_for_arg(
        &mut self,
        arg: &ArgSpec,
    ) -> Option<crate::pipeline::FieldSemantics> {
        self.derived()
            .field_semantics
            .get(&crate::pipeline::FieldInstanceId::from_arg(arg))
            .cloned()
    }

    pub(crate) fn field_can_edit(&mut self, arg: &ArgSpec) -> bool {
        self.field_semantics_for_arg(arg)
            .is_none_or(|semantics| semantics.can_edit)
    }

    pub(crate) fn field_required(&mut self, arg: &ArgSpec) -> bool {
        self.field_semantics_for_arg(arg)
            .map_or(arg.required, |semantics| semantics.required_badge)
    }
}

fn clamp_dropdown_scroll(current: usize, total_rows: usize, visible_rows: usize) -> usize {
    if total_rows == 0 || visible_rows == 0 {
        return 0;
    }
    current.min(total_rows.saturating_sub(visible_rows.min(total_rows)))
}

#[cfg(test)]
mod tests {
    use clap::{Arg, ArgAction, Command};

    use super::{AppState, ArgInput, ArgValue, InputSource};
    use crate::spec::{ArgKind, ArgSpec, CommandSpec, ValueCardinality};

    fn arg(id: &str, name: &str, kind: ArgKind) -> ArgSpec {
        ArgSpec {
            id: id.to_string(),
            display_name: name.to_string(),
            help: None,
            required: false,
            kind,
            default_values: Vec::new(),
            choices: Vec::new(),
            position: None,
            value_cardinality: ValueCardinality::One,
            value_hint: None,
            ..ArgSpec::default()
        }
    }

    #[test]
    fn new_state_initializes_materialized_defaults_without_normalize() {
        let root = CommandSpec {
            name: "tool".to_string(),
            version: None,
            about: None,
            help: String::new(),
            args: vec![arg("verbose", "--verbose", ArgKind::Flag)],
            subcommands: Vec::new(),
            ..CommandSpec::default()
        };

        let state = AppState::new(root);

        assert_eq!(
            state.domain.current_form().and_then(|form| {
                state
                    .domain
                    .current_command()
                    .args
                    .iter()
                    .find(|arg| arg.id == "verbose")
                    .and_then(|arg| form.compatibility_value(arg))
            }),
            Some(ArgValue::Bool(false))
        );
    }

    #[test]
    fn env_backed_defaults_remain_stable_after_env_source_changes() {
        let path = std::env::var("PATH").expect("PATH should exist for env-backed default tests");
        let root = CommandSpec::from_command(
            &Command::new("tool").arg(Arg::new("config").long("config").env("PATH")),
        );
        let mut state = AppState::new(root);
        let initial_form = state.domain.current_form().expect("materialized form");
        let config = state
            .domain
            .current_command()
            .args
            .iter()
            .find(|arg| arg.id == "config")
            .expect("config arg");
        assert_eq!(
            initial_form.compatibility_value(config),
            Some(ArgValue::Text(path.clone()))
        );
        assert_eq!(initial_form.input_source("config"), Some(InputSource::Env));

        state.domain.root.args[0].metadata.defaults.env =
            Some("CLAP_TUI_TEST_ENV_STABILITY_UNUSED".to_string());

        let current_form = state
            .domain
            .current_form()
            .expect("effective form remains available");
        let config = state
            .domain
            .current_command()
            .args
            .iter()
            .find(|arg| arg.id == "config")
            .expect("config arg");
        assert_eq!(
            current_form.compatibility_value(config),
            Some(ArgValue::Text(path))
        );
        assert_eq!(current_form.input_source("config"), Some(InputSource::Env));
    }

    #[test]
    fn clearing_last_optional_value_removes_empty_form_state() {
        let root = CommandSpec {
            name: "tool".to_string(),
            version: None,
            about: None,
            help: String::new(),
            args: vec![arg("target", "--target", ArgKind::Option)],
            subcommands: Vec::new(),
            ..CommandSpec::default()
        };
        let mut state = AppState::new(root);

        state.domain.set_text_value("target", "value");
        state.domain.mark_touched("target");
        state.domain.clear_value_and_untouch("target");

        assert!(state.domain.current_form().is_none());
    }

    #[test]
    fn global_values_are_stored_on_owner_and_projected_into_descendants() {
        let root = CommandSpec::from_command(
            &Command::new("tool")
                .arg(
                    Arg::new("verbose")
                        .long("verbose")
                        .action(ArgAction::SetTrue)
                        .global(true),
                )
                .subcommand(
                    Command::new("build")
                        .arg(Arg::new("target").long("target"))
                        .subcommand(Command::new("release")),
                ),
        );
        let mut state = AppState::new(root);
        state
            .select_command_path(&["build".to_string(), "release".to_string()])
            .expect("valid path");

        state.domain.toggle_flag_touched("verbose");

        let root_form = state
            .domain
            .forms
            .get("tool")
            .expect("root form should own global state");
        let verbose = root_form
            .inputs
            .get("verbose")
            .expect("global flag should be stored");
        assert_eq!(
            verbose,
            &super::ArgInputState {
                value: ArgInput::Flag {
                    present: true,
                    source: InputSource::User,
                },
                touched: true,
            }
        );

        let effective = state.domain.current_form().expect("effective form");
        let verbose_arg = state
            .domain
            .current_command()
            .args
            .iter()
            .find(|arg| arg.id == "verbose")
            .expect("verbose arg");
        assert_eq!(
            effective.compatibility_value(verbose_arg),
            Some(ArgValue::Bool(true))
        );
    }

    #[test]
    fn optional_value_with_choices_stays_text_compatible_for_editing() {
        let mut color = arg("color", "--color", ArgKind::Flag);
        color.metadata.action.value_arity = crate::spec::ArgValueArity::Optional;
        color.choices = vec![
            "auto".to_string(),
            "always".to_string(),
            "never".to_string(),
        ];
        let root = CommandSpec {
            name: "tool".to_string(),
            version: None,
            about: None,
            help: String::new(),
            args: vec![color.clone()],
            subcommands: Vec::new(),
            ..CommandSpec::default()
        };
        let mut state = AppState::new(root);

        state.domain.set_text_value("color", "n");
        state.domain.mark_touched("color");

        assert_eq!(
            state
                .domain
                .current_form()
                .and_then(|form| form.compatibility_value(&color)),
            Some(ArgValue::Text("n".to_string()))
        );
    }

    #[test]
    fn invocation_helpers_cover_append_reorder_and_counter_mutations() {
        let root = CommandSpec::from_command(
            &Command::new("tool")
                .arg(
                    Arg::new("include")
                        .long("include")
                        .action(ArgAction::Append)
                        .num_args(1..),
                )
                .arg(Arg::new("verbose").short('v').action(ArgAction::Count)),
        );
        let mut state = AppState::new(root);

        state
            .domain
            .replace_values("include", vec!["src".to_string()]);
        state.domain.append_value("include", "tests".to_string());
        state.domain.reorder_occurrence("include", 1, 0);
        state.domain.remove_value("include", 1, 0);
        state.domain.increment_counter("verbose");
        state.domain.increment_counter("verbose");
        state.domain.decrement_counter("verbose");
        state.domain.toggle_optional_value_flag("verbose", true);

        let invocation = state.domain.resolve_invocation_state();
        assert_eq!(invocation.len(), 1);
        assert_eq!(state.domain.command_path_key(), "tool");

        let include = invocation[0]
            .1
            .inputs
            .get("include")
            .expect("include input should exist");
        match &include.value {
            ArgInput::Values { occurrences } => {
                assert_eq!(occurrences.len(), 1);
                assert_eq!(occurrences[0].values, vec!["tests".to_string()]);
            }
            other => panic!("expected values input, got {other:?}"),
        }
    }
}
