//! Keyboard-only presentation state for storage-owned write workflows.

use std::{fmt, num::NonZeroUsize};

use crossterm::event::{KeyCode, KeyModifiers};
use ironstorage::{
    command::{
        CopyRequest, GenerateRequest, GeneratedPresentation, GrepRequest, InitRequest, InsertInput,
        InsertRequest, MoveRequest, OtpAppendRequest, OtpInputSource, OtpInsertRequest,
        RemoveRequest,
    },
    crypto::KeyInfo,
    kdbx::{KdbxImportMode, KdbxImportRequest},
    otp::OtpInput,
    repository::SecretBytes,
    write::{InsertContent, OverwriteDecision},
};
use zeroize::Zeroize;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InsertMode {
    Hidden,
    Echoed,
    Multiline,
}

impl InsertMode {
    fn next(self) -> Self {
        match self {
            Self::Hidden => Self::Echoed,
            Self::Echoed => Self::Multiline,
            Self::Multiline => Self::Hidden,
        }
    }

    pub fn label(self) -> &'static str {
        match self {
            Self::Hidden => "single line (confirmed)",
            Self::Echoed => "single line",
            Self::Multiline => "multiline",
        }
    }
}

#[derive(Default)]
struct SecretText {
    characters: Vec<char>,
}

impl SecretText {
    fn push(&mut self, character: char) {
        self.characters.push(character);
    }

    fn pop(&mut self) {
        self.characters.pop();
    }

    fn bytes(&self) -> Vec<u8> {
        self.characters.iter().collect::<String>().into_bytes()
    }

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

impl Drop for SecretText {
    fn drop(&mut self) {
        self.characters.zeroize();
    }
}

impl fmt::Debug for SecretText {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("SecretText([REDACTED])")
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct RecipientChoice {
    identity: String,
    label: String,
    selected: bool,
}

#[derive(Debug)]
pub struct InitForm {
    path: String,
    recipients: Vec<RecipientChoice>,
    focus: usize,
}

#[derive(Debug)]
pub struct InsertForm {
    entry: String,
    mode: InsertMode,
    secret: SecretText,
    confirmation: SecretText,
    overwrite: bool,
    focus: usize,
}

#[derive(Debug)]
pub struct GenerateForm {
    entry: String,
    length: String,
    no_symbols: bool,
    overwrite: bool,
    in_place: bool,
    presentation: GeneratedPresentation,
    focus: usize,
}

#[derive(Debug)]
pub struct GrepForm {
    pattern: String,
    ignore_case: bool,
    invert_match: bool,
    line_number: bool,
    fixed_strings: bool,
    focus: usize,
}

#[derive(Debug)]
pub struct KdbxForm {
    source: String,
    key_file: String,
    password: SecretText,
    quick_add: bool,
    confirmed: bool,
    focus: usize,
}

#[derive(Debug)]
pub struct RemoveForm {
    target: String,
    recursive: bool,
    force: bool,
    confirmed: bool,
    focus: usize,
}

#[derive(Debug)]
pub struct TransferForm {
    source: String,
    destination: String,
    destination_directory: bool,
    overwrite: bool,
    focus: usize,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OtpFormKind {
    Insert,
    Append,
    Validate,
}

#[derive(Debug)]
pub struct OtpForm {
    kind: OtpFormKind,
    entry: String,
    uri: SecretText,
    force: bool,
    confirmed: bool,
    focus: usize,
}

#[derive(Debug)]
pub enum WorkflowForm {
    Init(InitForm),
    Insert(InsertForm),
    Generate(GenerateForm),
    Grep(GrepForm),
    Kdbx(KdbxForm),
    Remove(RemoveForm),
    Move(TransferForm),
    Copy(TransferForm),
    Otp(OtpForm),
}

#[derive(Debug)]
pub enum WorkflowSubmission {
    Init(InitRequest),
    Insert {
        request: InsertRequest,
        contents: InsertContent,
        overwrite: OverwriteDecision,
    },
    Generate {
        request: GenerateRequest,
        overwrite: OverwriteDecision,
    },
    Grep(GrepRequest),
    Kdbx {
        request: KdbxImportRequest,
        password: SecretBytes,
    },
    Remove(RemoveRequest),
    Move {
        request: MoveRequest,
        overwrite: OverwriteDecision,
    },
    Copy {
        request: CopyRequest,
        overwrite: OverwriteDecision,
    },
    OtpInsert {
        request: OtpInsertRequest,
        input: OtpInput,
    },
    OtpAppend {
        request: OtpAppendRequest,
        input: OtpInput,
    },
    OtpValidate {
        uri: OtpInput,
    },
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum WorkflowInput {
    Consumed,
    Cancel,
    Lock,
    Submit,
}

impl WorkflowForm {
    pub fn otp(kind: OtpFormKind, entry: Option<String>, force: bool) -> Self {
        Self::Otp(OtpForm {
            kind,
            entry: entry.unwrap_or_default(),
            uri: SecretText::default(),
            force,
            confirmed: false,
            focus: 0,
        })
    }
    pub fn init(keys: &[KeyInfo], default: Option<&KeyInfo>, request: Option<InitRequest>) -> Self {
        let request = request.unwrap_or_else(|| InitRequest {
            path: None,
            key_identities: default
                .map(|key| vec![key.fingerprint().to_string()])
                .unwrap_or_default(),
        });
        let mut recipients = keys
            .iter()
            .filter(|key| key.can_encrypt())
            .map(|key| {
                let identity = key.fingerprint().to_string();
                let user = key
                    .user_ids()
                    .first()
                    .map_or("unknown identity", String::as_str);
                RecipientChoice {
                    selected: request.key_identities.iter().any(|item| item == &identity),
                    label: format!("{} — {user}", key.key_id()),
                    identity,
                }
            })
            .collect::<Vec<_>>();
        for identity in &request.key_identities {
            if !recipients.iter().any(|choice| &choice.identity == identity) {
                recipients.push(RecipientChoice {
                    identity: identity.clone(),
                    label: format!("requested identity {identity}"),
                    selected: true,
                });
            }
        }
        Self::Init(InitForm {
            path: request.path.unwrap_or_default(),
            recipients,
            focus: 0,
        })
    }

    pub fn insert(request: Option<InsertRequest>) -> Self {
        let request = request.unwrap_or(InsertRequest {
            entry: String::new(),
            input: InsertInput::HiddenConfirmed,
            force: false,
        });
        let mode = match request.input {
            InsertInput::HiddenConfirmed => InsertMode::Hidden,
            InsertInput::EchoedLine => InsertMode::Echoed,
            InsertInput::Multiline => InsertMode::Multiline,
        };
        Self::Insert(InsertForm {
            entry: request.entry,
            mode,
            secret: SecretText::default(),
            confirmation: SecretText::default(),
            overwrite: request.force,
            focus: 0,
        })
    }

    pub fn generate(request: Option<GenerateRequest>) -> Self {
        let request = request.unwrap_or(GenerateRequest {
            entry: String::new(),
            length: None,
            no_symbols: false,
            force: false,
            in_place: false,
            presentation: GeneratedPresentation::Terminal,
        });
        Self::Generate(GenerateForm {
            entry: request.entry,
            length: request
                .length
                .map_or_else(String::new, |length| length.to_string()),
            no_symbols: request.no_symbols,
            overwrite: request.force,
            in_place: request.in_place,
            presentation: request.presentation,
            focus: 0,
        })
    }

    pub fn grep(request: Option<GrepRequest>) -> Self {
        let request = request.unwrap_or(GrepRequest {
            pattern: String::new(),
            ignore_case: false,
            invert_match: false,
            line_number: true,
            fixed_strings: false,
        });
        Self::Grep(GrepForm {
            pattern: request.pattern,
            ignore_case: request.ignore_case,
            invert_match: request.invert_match,
            line_number: request.line_number,
            fixed_strings: request.fixed_strings,
            focus: 0,
        })
    }

    pub fn kdbx(request: Option<KdbxImportRequest>) -> Self {
        let request = request
            .unwrap_or_else(|| KdbxImportRequest::new("", None, KdbxImportMode::AddAndUpdate));
        Self::Kdbx(KdbxForm {
            source: request.source().to_string_lossy().into_owned(),
            key_file: request
                .key_file()
                .map_or_else(String::new, |path| path.to_string_lossy().into_owned()),
            password: SecretText::default(),
            quick_add: request.mode() == KdbxImportMode::QuickAdd,
            confirmed: false,
            focus: 0,
        })
    }

    pub fn remove(request: Option<RemoveRequest>, selected: Option<(&str, bool)>) -> Self {
        let request = request.unwrap_or_else(|| RemoveRequest {
            entry: selected.map_or_else(String::new, |(path, _)| path.to_owned()),
            recursive: selected.is_some_and(|(_, directory)| directory),
            force: false,
        });
        Self::Remove(RemoveForm {
            target: request.entry,
            recursive: request.recursive,
            force: request.force,
            confirmed: false,
            focus: 0,
        })
    }

    pub fn move_entry(request: Option<MoveRequest>, selected: Option<&str>) -> Self {
        let request = request.unwrap_or_else(|| MoveRequest {
            source: selected.unwrap_or_default().to_owned(),
            destination: String::new(),
            force: false,
        });
        Self::Move(transfer_form(
            request.source,
            request.destination,
            request.force,
        ))
    }

    pub fn copy_entry(request: Option<CopyRequest>, selected: Option<&str>) -> Self {
        let request = request.unwrap_or_else(|| CopyRequest {
            source: selected.unwrap_or_default().to_owned(),
            destination: String::new(),
            force: false,
        });
        Self::Copy(transfer_form(
            request.source,
            request.destination,
            request.force,
        ))
    }

    pub fn title(&self) -> &'static str {
        match self {
            Self::Init(_) => "Initialize recipients",
            Self::Insert(_) => "Insert entry",
            Self::Generate(_) => "Generate password",
            Self::Grep(_) => "Search decrypted entries",
            Self::Kdbx(_) => "Import KeePass KDBX",
            Self::Remove(_) => "Remove entry or folder",
            Self::Move(_) => "Move or rename",
            Self::Copy(_) => "Copy entry or folder",
            Self::Otp(form) => match form.kind {
                OtpFormKind::Insert => "Insert OTP URI",
                OtpFormKind::Append => "Append OTP URI",
                OtpFormKind::Validate => "Validate OTP URI",
            },
        }
    }

    pub fn rows(&self) -> Vec<String> {
        match self {
            Self::Init(form) => {
                let mut rows = vec![row(
                    form.focus == 0,
                    "Path",
                    if form.path.is_empty() {
                        "/"
                    } else {
                        &form.path
                    },
                )];
                rows.push("Recipients (Space toggles):".to_owned());
                rows.extend(form.recipients.iter().enumerate().map(|(index, choice)| {
                    row(
                        form.focus == index + 1,
                        if choice.selected { "[x]" } else { "[ ]" },
                        &choice.label,
                    )
                }));
                rows
            }
            Self::Insert(form) => vec![
                row(form.focus == 0, "Entry", &form.entry),
                row(form.focus == 1, "Input", form.mode.label()),
                row(
                    form.focus == 2,
                    "Secret",
                    if form.secret.is_empty() {
                        "(empty)"
                    } else {
                        "••••••••"
                    },
                ),
                row(
                    form.focus == 3,
                    "Confirm",
                    if form.mode != InsertMode::Hidden {
                        "(not required)"
                    } else if form.confirmation.is_empty() {
                        "(empty)"
                    } else {
                        "••••••••"
                    },
                ),
                row(form.focus == 4, "Overwrite", yes_no(form.overwrite)),
            ],
            Self::Generate(form) => vec![
                row(form.focus == 0, "Entry", &form.entry),
                row(
                    form.focus == 1,
                    "Length",
                    if form.length.is_empty() {
                        "pass default"
                    } else {
                        &form.length
                    },
                ),
                row(form.focus == 2, "Symbols", yes_no(!form.no_symbols)),
                row(form.focus == 3, "Overwrite", yes_no(form.overwrite)),
                row(form.focus == 4, "In place", yes_no(form.in_place)),
                row(
                    form.focus == 5,
                    "Presentation",
                    generated_presentation_label(form.presentation),
                ),
            ],
            Self::Grep(form) => vec![
                row(form.focus == 0, "Pattern", &form.pattern),
                row(form.focus == 1, "Ignore case", yes_no(form.ignore_case)),
                row(form.focus == 2, "Invert match", yes_no(form.invert_match)),
                row(form.focus == 3, "Line numbers", yes_no(form.line_number)),
                row(form.focus == 4, "Fixed string", yes_no(form.fixed_strings)),
            ],
            Self::Kdbx(form) => vec![
                row(form.focus == 0, "KDBX file", &form.source),
                row(
                    form.focus == 1,
                    "Key file",
                    if form.key_file.is_empty() {
                        "(none)"
                    } else {
                        &form.key_file
                    },
                ),
                row(
                    form.focus == 2,
                    "Database password",
                    if form.password.is_empty() {
                        "(empty)"
                    } else {
                        "••••••••"
                    },
                ),
                row(
                    form.focus == 3,
                    "Only add new entries",
                    yes_no(form.quick_add),
                ),
                row(form.focus == 4, "Confirm import", yes_no(form.confirmed)),
            ],
            Self::Remove(form) => vec![
                row(form.focus == 0, "Target", &form.target),
                row(form.focus == 1, "Recursive folder", yes_no(form.recursive)),
                row(form.focus == 2, "Force", yes_no(form.force)),
                row(
                    form.focus == 3,
                    "Confirm permanent removal",
                    yes_no(form.confirmed),
                ),
            ],
            Self::Move(form) | Self::Copy(form) => vec![
                row(form.focus == 0, "Source", &form.source),
                row(form.focus == 1, "Destination", &form.destination),
                row(
                    form.focus == 2,
                    "Destination is existing directory",
                    yes_no(form.destination_directory),
                ),
                row(
                    form.focus == 3,
                    "Overwrite collision",
                    yes_no(form.overwrite),
                ),
            ],
            Self::Otp(form) => {
                vec![
                    row(
                        form.focus == 0,
                        "Entry",
                        if form.kind == OtpFormKind::Validate {
                            "(validation only)"
                        } else if form.entry.is_empty() {
                            "(derive from URI)"
                        } else {
                            &form.entry
                        },
                    ),
                    row(
                        form.focus == 1,
                        "OTP URI",
                        if form.uri.is_empty() {
                            "(empty)"
                        } else {
                            "••••••••"
                        },
                    ),
                    row(
                        form.focus == 2,
                        "Force replacement",
                        if form.kind == OtpFormKind::Validate {
                            "(not used)"
                        } else {
                            yes_no(form.force)
                        },
                    ),
                    row(
                        form.focus == 3,
                        "Confirm write",
                        if form.kind == OtpFormKind::Validate {
                            "(not required)"
                        } else {
                            yes_no(form.confirmed)
                        },
                    ),
                ]
            }
        }
    }

    pub fn handle_key(&mut self, code: KeyCode, modifiers: KeyModifiers) -> WorkflowInput {
        if modifiers.contains(KeyModifiers::CONTROL) {
            return match code {
                KeyCode::Char('s') => WorkflowInput::Submit,
                KeyCode::Char('l' | 'z') => WorkflowInput::Lock,
                _ => WorkflowInput::Consumed,
            };
        }
        match code {
            KeyCode::Esc => return WorkflowInput::Cancel,
            KeyCode::Tab | KeyCode::Down => self.move_focus(true),
            KeyCode::BackTab | KeyCode::Up => self.move_focus(false),
            KeyCode::Char(' ') => self.space(),
            KeyCode::Enter => self.enter(),
            KeyCode::Backspace => self.backspace(),
            KeyCode::Char(character) if !character.is_control() => self.character(character),
            _ => {}
        }
        WorkflowInput::Consumed
    }

    pub fn submission(&self) -> Result<WorkflowSubmission, String> {
        match self {
            Self::Init(form) => {
                let key_identities = form
                    .recipients
                    .iter()
                    .filter(|choice| choice.selected)
                    .map(|choice| choice.identity.clone())
                    .collect::<Vec<_>>();
                if key_identities.is_empty() {
                    return Err("Select at least one encryption recipient".to_owned());
                }
                Ok(WorkflowSubmission::Init(InitRequest {
                    path: (!form.path.trim().is_empty()).then(|| form.path.trim().to_owned()),
                    key_identities,
                }))
            }
            Self::Insert(form) => {
                if form.entry.trim().is_empty() {
                    return Err("Entry path is required".to_owned());
                }
                let (input, contents) = match form.mode {
                    InsertMode::Hidden => (
                        InsertInput::HiddenConfirmed,
                        InsertContent::hidden(form.secret.bytes(), form.confirmation.bytes()),
                    ),
                    InsertMode::Echoed => (
                        InsertInput::EchoedLine,
                        InsertContent::echoed(form.secret.bytes()),
                    ),
                    InsertMode::Multiline => (
                        InsertInput::Multiline,
                        Ok(InsertContent::multiline(form.secret.bytes())),
                    ),
                };
                Ok(WorkflowSubmission::Insert {
                    request: InsertRequest {
                        entry: form.entry.trim().to_owned(),
                        input,
                        force: form.overwrite,
                    },
                    contents: contents.map_err(|error| error.to_string())?,
                    overwrite: if form.overwrite {
                        OverwriteDecision::Allow
                    } else {
                        OverwriteDecision::Decline
                    },
                })
            }
            Self::Generate(form) => {
                if form.entry.trim().is_empty() {
                    return Err("Entry path is required".to_owned());
                }
                let length = if form.length.is_empty() {
                    None
                } else {
                    Some(
                        form.length
                            .parse::<NonZeroUsize>()
                            .map_err(|_| "Length must be a positive integer".to_owned())?,
                    )
                };
                Ok(WorkflowSubmission::Generate {
                    request: GenerateRequest {
                        entry: form.entry.trim().to_owned(),
                        length,
                        no_symbols: form.no_symbols,
                        force: form.overwrite,
                        in_place: form.in_place,
                        presentation: form.presentation,
                    },
                    overwrite: if form.overwrite || form.in_place {
                        OverwriteDecision::Allow
                    } else {
                        OverwriteDecision::Decline
                    },
                })
            }
            Self::Grep(form) => {
                if form.pattern.is_empty() {
                    return Err("Search pattern is required".to_owned());
                }
                Ok(WorkflowSubmission::Grep(GrepRequest {
                    pattern: form.pattern.clone(),
                    ignore_case: form.ignore_case,
                    invert_match: form.invert_match,
                    line_number: form.line_number,
                    fixed_strings: form.fixed_strings,
                }))
            }
            Self::Kdbx(form) => {
                if form.source.trim().is_empty() {
                    return Err("KDBX file path is required".to_owned());
                }
                if !form.confirmed {
                    return Err("Explicitly confirm the KDBX import".to_owned());
                }
                Ok(WorkflowSubmission::Kdbx {
                    request: KdbxImportRequest::new(
                        form.source.trim(),
                        (!form.key_file.trim().is_empty()).then(|| form.key_file.trim().into()),
                        if form.quick_add {
                            KdbxImportMode::QuickAdd
                        } else {
                            KdbxImportMode::AddAndUpdate
                        },
                    ),
                    password: SecretBytes::new(form.password.bytes()),
                })
            }
            Self::Remove(form) => {
                if form.target.trim().is_empty() {
                    return Err("Removal target is required".to_owned());
                }
                if !form.confirmed {
                    return Err("Explicitly confirm permanent removal".to_owned());
                }
                Ok(WorkflowSubmission::Remove(RemoveRequest {
                    entry: form.target.trim().to_owned(),
                    recursive: form.recursive,
                    force: form.force,
                }))
            }
            Self::Move(form) => {
                let (source, destination, overwrite) = transfer_submission(form)?;
                Ok(WorkflowSubmission::Move {
                    request: MoveRequest {
                        source,
                        destination,
                        force: form.overwrite,
                    },
                    overwrite,
                })
            }
            Self::Copy(form) => {
                let (source, destination, overwrite) = transfer_submission(form)?;
                Ok(WorkflowSubmission::Copy {
                    request: CopyRequest {
                        source,
                        destination,
                        force: form.overwrite,
                    },
                    overwrite,
                })
            }
            Self::Otp(form) => {
                if form.uri.is_empty() {
                    return Err("OTP URI is required".to_owned());
                }
                let input = OtpInput::line(form.uri.bytes()).map_err(|error| error.to_string())?;
                match form.kind {
                    OtpFormKind::Validate => Ok(WorkflowSubmission::OtpValidate { uri: input }),
                    OtpFormKind::Insert => {
                        if !form.confirmed {
                            return Err("Explicitly confirm the OTP write".to_owned());
                        }
                        Ok(WorkflowSubmission::OtpInsert {
                            request: OtpInsertRequest {
                                entry: (!form.entry.trim().is_empty())
                                    .then(|| form.entry.trim().to_owned()),
                                force: form.force,
                                echo: false,
                                source: OtpInputSource::Uri,
                            },
                            input,
                        })
                    }
                    OtpFormKind::Append => {
                        if form.entry.trim().is_empty() {
                            return Err("Entry path is required".to_owned());
                        }
                        if !form.confirmed {
                            return Err("Explicitly confirm the OTP write".to_owned());
                        }
                        Ok(WorkflowSubmission::OtpAppend {
                            request: OtpAppendRequest {
                                entry: form.entry.trim().to_owned(),
                                force: form.force,
                                echo: false,
                                source: OtpInputSource::Uri,
                            },
                            input,
                        })
                    }
                }
            }
        }
    }

    fn focus_count(&self) -> usize {
        match self {
            Self::Init(form) => form.recipients.len() + 1,
            Self::Insert(_) => 5,
            Self::Generate(_) => 6,
            Self::Grep(_) => 5,
            Self::Kdbx(_) => 5,
            Self::Remove(_) | Self::Move(_) | Self::Copy(_) => 4,
            Self::Otp(_) => 4,
        }
    }

    fn move_focus(&mut self, forward: bool) {
        let count = self.focus_count();
        let focus = match self {
            Self::Init(form) => &mut form.focus,
            Self::Insert(form) => &mut form.focus,
            Self::Generate(form) => &mut form.focus,
            Self::Grep(form) => &mut form.focus,
            Self::Kdbx(form) => &mut form.focus,
            Self::Remove(form) => &mut form.focus,
            Self::Move(form) | Self::Copy(form) => &mut form.focus,
            Self::Otp(form) => &mut form.focus,
        };
        *focus = if forward {
            (*focus + 1) % count
        } else {
            (*focus + count - 1) % count
        };
    }

    fn space(&mut self) {
        match self {
            Self::Init(form) if form.focus > 0 => form.recipients[form.focus - 1].selected ^= true,
            Self::Insert(form) if form.focus == 1 => form.mode = form.mode.next(),
            Self::Insert(form) if form.focus == 4 => form.overwrite ^= true,
            Self::Generate(form) if form.focus == 2 => form.no_symbols ^= true,
            Self::Generate(form) if form.focus == 3 => {
                form.overwrite ^= true;
                if form.overwrite {
                    form.in_place = false;
                }
            }
            Self::Generate(form) if form.focus == 4 => {
                form.in_place ^= true;
                if form.in_place {
                    form.overwrite = false;
                }
            }
            Self::Generate(form) if form.focus == 5 => {
                form.presentation = match form.presentation {
                    GeneratedPresentation::Terminal => GeneratedPresentation::Clipboard,
                    GeneratedPresentation::Clipboard => GeneratedPresentation::QrCode,
                    GeneratedPresentation::QrCode => GeneratedPresentation::Terminal,
                }
            }
            Self::Grep(form) if form.focus == 1 => form.ignore_case ^= true,
            Self::Grep(form) if form.focus == 2 => form.invert_match ^= true,
            Self::Grep(form) if form.focus == 3 => form.line_number ^= true,
            Self::Grep(form) if form.focus == 4 => form.fixed_strings ^= true,
            Self::Kdbx(form) if form.focus == 3 => form.quick_add ^= true,
            Self::Kdbx(form) if form.focus == 4 => form.confirmed ^= true,
            Self::Remove(form) if form.focus == 1 => form.recursive ^= true,
            Self::Remove(form) if form.focus == 2 => form.force ^= true,
            Self::Remove(form) if form.focus == 3 => form.confirmed ^= true,
            Self::Move(form) | Self::Copy(form) if form.focus == 2 => {
                form.destination_directory ^= true
            }
            Self::Move(form) | Self::Copy(form) if form.focus == 3 => form.overwrite ^= true,
            Self::Otp(form) if form.focus == 2 => form.force ^= true,
            Self::Otp(form) if form.focus == 3 => form.confirmed ^= true,
            _ => self.character(' '),
        }
    }

    fn enter(&mut self) {
        match self {
            Self::Insert(form) if form.focus == 2 && form.mode == InsertMode::Multiline => {
                form.secret.push('\n')
            }
            _ => self.space(),
        }
    }

    fn backspace(&mut self) {
        match self {
            Self::Init(form) if form.focus == 0 => {
                form.path.pop();
            }
            Self::Insert(form) if form.focus == 0 => {
                form.entry.pop();
            }
            Self::Insert(form) if form.focus == 2 => form.secret.pop(),
            Self::Insert(form) if form.focus == 3 && form.mode == InsertMode::Hidden => {
                form.confirmation.pop()
            }
            Self::Generate(form) if form.focus == 0 => {
                form.entry.pop();
            }
            Self::Generate(form) if form.focus == 1 => {
                form.length.pop();
            }
            Self::Grep(form) if form.focus == 0 => {
                form.pattern.pop();
            }
            Self::Kdbx(form) if form.focus == 0 => {
                form.source.pop();
            }
            Self::Kdbx(form) if form.focus == 1 => {
                form.key_file.pop();
            }
            Self::Kdbx(form) if form.focus == 2 => form.password.pop(),
            Self::Remove(form) if form.focus == 0 => {
                form.target.pop();
            }
            Self::Move(form) | Self::Copy(form) if form.focus == 0 => {
                form.source.pop();
            }
            Self::Move(form) | Self::Copy(form) if form.focus == 1 => {
                form.destination.pop();
            }
            Self::Otp(form) if form.focus == 0 => {
                form.entry.pop();
            }
            Self::Otp(form) if form.focus == 1 => {
                form.uri.pop();
            }
            _ => {}
        }
    }

    fn character(&mut self, character: char) {
        match self {
            Self::Init(form) if form.focus == 0 => form.path.push(character),
            Self::Insert(form) if form.focus == 0 => form.entry.push(character),
            Self::Insert(form)
                if form.focus == 2 && (form.mode == InsertMode::Multiline || character != '\n') =>
            {
                form.secret.push(character)
            }
            Self::Insert(form)
                if form.focus == 3 && form.mode == InsertMode::Hidden && character != '\n' =>
            {
                form.confirmation.push(character)
            }
            Self::Generate(form) if form.focus == 0 => form.entry.push(character),
            Self::Generate(form) if form.focus == 1 && character.is_ascii_digit() => {
                form.length.push(character)
            }
            Self::Grep(form) if form.focus == 0 => form.pattern.push(character),
            Self::Kdbx(form) if form.focus == 0 => form.source.push(character),
            Self::Kdbx(form) if form.focus == 1 => form.key_file.push(character),
            Self::Kdbx(form) if form.focus == 2 && character != '\n' => {
                form.password.push(character)
            }
            Self::Remove(form) if form.focus == 0 => form.target.push(character),
            Self::Move(form) | Self::Copy(form) if form.focus == 0 => form.source.push(character),
            Self::Move(form) | Self::Copy(form) if form.focus == 1 => {
                form.destination.push(character)
            }
            Self::Otp(form) if form.focus == 0 => form.entry.push(character),
            Self::Otp(form) if form.focus == 1 && character != '\n' => form.uri.push(character),
            _ => {}
        }
    }
}

fn transfer_form(source: String, mut destination: String, overwrite: bool) -> TransferForm {
    let destination_directory = destination.ends_with('/');
    while destination.ends_with('/') {
        destination.pop();
    }
    TransferForm {
        source,
        destination,
        destination_directory,
        overwrite,
        focus: 0,
    }
}

fn transfer_submission(form: &TransferForm) -> Result<(String, String, OverwriteDecision), String> {
    if form.source.trim().is_empty() {
        return Err("Source path is required".to_owned());
    }
    if form.destination.trim().is_empty() {
        return Err("Destination path is required".to_owned());
    }
    let mut destination = form.destination.trim().to_owned();
    if form.destination_directory {
        destination.push('/');
    }
    Ok((
        form.source.trim().to_owned(),
        destination,
        if form.overwrite {
            OverwriteDecision::Allow
        } else {
            OverwriteDecision::Decline
        },
    ))
}

fn row(focused: bool, label: &str, value: &str) -> String {
    format!("{} {label}: {value}", if focused { ">" } else { " " })
}

fn yes_no(value: bool) -> &'static str {
    if value { "yes" } else { "no" }
}

fn generated_presentation_label(presentation: GeneratedPresentation) -> &'static str {
    match presentation {
        GeneratedPresentation::Terminal => "unlocked entry viewer",
        GeneratedPresentation::Clipboard => "clipboard with timeout",
        GeneratedPresentation::QrCode => "terminal QR",
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn kdbx_form_keeps_password_secret_and_submits_both_import_modes() {
        let mut form = WorkflowForm::kdbx(Some(KdbxImportRequest::new(
            "fixture.kdbx",
            Some("fixture.key".into()),
            KdbxImportMode::AddAndUpdate,
        )));
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        for character in "database password".chars() {
            form.handle_key(KeyCode::Char(character), KeyModifiers::NONE);
        }
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        form.handle_key(KeyCode::Char(' '), KeyModifiers::NONE);
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        form.handle_key(KeyCode::Char(' '), KeyModifiers::NONE);

        let WorkflowSubmission::Kdbx { request, password } =
            form.submission().expect("confirmed import")
        else {
            panic!("expected KDBX submission");
        };
        assert_eq!(request.source(), std::path::Path::new("fixture.kdbx"));
        assert_eq!(
            request.key_file(),
            Some(std::path::Path::new("fixture.key"))
        );
        assert_eq!(request.mode(), KdbxImportMode::QuickAdd);
        assert_eq!(password.expose(), b"database password");
    }

    fn type_text(form: &mut WorkflowForm, text: &str) {
        for character in text.chars() {
            form.handle_key(KeyCode::Char(character), KeyModifiers::NONE);
        }
    }

    #[test]
    fn hidden_entry_is_confirmed_and_never_rendered_or_debugged() {
        let mut form = WorkflowForm::insert(None);
        type_text(&mut form, "nested/account");
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        type_text(&mut form, "do-not-display");
        form.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        type_text(&mut form, "do-not-display");
        let rendered = form.rows().join("\n");
        let debug = format!("{form:?}");
        assert!(!rendered.contains("do-not-display"));
        assert!(!debug.contains("do-not-display"));
        assert!(matches!(
            form.submission(),
            Ok(WorkflowSubmission::Insert { .. })
        ));
    }

    #[test]
    fn confirmation_and_generation_validation_are_storage_compatible() {
        let mut insert = WorkflowForm::insert(None);
        type_text(&mut insert, "entry");
        insert.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        insert.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        type_text(&mut insert, "first");
        insert.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        type_text(&mut insert, "second");
        assert!(insert.submission().unwrap_err().contains("match"));

        let mut generate = WorkflowForm::generate(None);
        type_text(&mut generate, "entry");
        generate.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        assert!(generate.submission().is_ok());
        type_text(&mut generate, "0");
        assert!(generate.submission().unwrap_err().contains("positive"));

        let qr = WorkflowForm::generate(Some(GenerateRequest {
            entry: "entry".to_owned(),
            length: None,
            no_symbols: false,
            force: false,
            in_place: false,
            presentation: GeneratedPresentation::QrCode,
        }));
        assert!(matches!(
            qr.submission(),
            Ok(WorkflowSubmission::Generate {
                request: GenerateRequest {
                    presentation: GeneratedPresentation::QrCode,
                    ..
                },
                ..
            })
        ));
    }

    #[test]
    fn multiline_input_and_generate_options_are_preserved() {
        let mut insert = WorkflowForm::insert(Some(InsertRequest {
            entry: "notes/item".into(),
            input: InsertInput::Multiline,
            force: true,
        }));
        insert.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        insert.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        type_text(&mut insert, "first");
        insert.handle_key(KeyCode::Enter, KeyModifiers::NONE);
        type_text(&mut insert, "second");
        match insert.submission().expect("submission") {
            WorkflowSubmission::Insert {
                contents,
                overwrite,
                ..
            } => {
                assert_eq!(contents.expose(), b"first\nsecond");
                assert_eq!(overwrite, OverwriteDecision::Allow);
            }
            _ => panic!("wrong submission"),
        }
    }

    #[test]
    fn search_and_mutation_forms_preserve_every_explicit_storage_choice() {
        let grep = WorkflowForm::grep(Some(GrepRequest {
            pattern: "literal.*value".to_owned(),
            ignore_case: true,
            invert_match: true,
            line_number: true,
            fixed_strings: true,
        }));
        assert!(matches!(
            grep.submission(),
            Ok(WorkflowSubmission::Grep(GrepRequest {
                ignore_case: true,
                invert_match: true,
                line_number: true,
                fixed_strings: true,
                ..
            }))
        ));

        let mut remove = WorkflowForm::remove(
            Some(RemoveRequest {
                entry: "nested/folder".to_owned(),
                recursive: true,
                force: true,
            }),
            None,
        );
        assert!(remove.submission().unwrap_err().contains("confirm"));
        for _ in 0..3 {
            remove.handle_key(KeyCode::Tab, KeyModifiers::NONE);
        }
        remove.handle_key(KeyCode::Char(' '), KeyModifiers::NONE);
        assert!(matches!(
            remove.submission(),
            Ok(WorkflowSubmission::Remove(RemoveRequest {
                recursive: true,
                force: true,
                ..
            }))
        ));

        let copied = WorkflowForm::copy_entry(
            Some(CopyRequest {
                source: "source/entry".to_owned(),
                destination: "existing/folder/".to_owned(),
                force: true,
            }),
            None,
        );
        assert!(
            copied
                .rows()
                .join("\n")
                .contains("Destination is existing directory: yes")
        );
        assert!(matches!(
            copied.submission(),
            Ok(WorkflowSubmission::Copy {
                request: CopyRequest {
                    ref destination,
                    force: true,
                    ..
                },
                overwrite: OverwriteDecision::Allow,
            }) if destination == "existing/folder/"
        ));

        let moved = WorkflowForm::move_entry(
            Some(MoveRequest {
                source: "old".to_owned(),
                destination: "new".to_owned(),
                force: false,
            }),
            None,
        );
        assert!(matches!(
            moved.submission(),
            Ok(WorkflowSubmission::Move {
                overwrite: OverwriteDecision::Decline,
                ..
            })
        ));
    }
}
