//! In-process structured document editing state.

use std::fmt;

use ironstorage::{
    document::{
        DocumentError, EntryDocument, EntryField, EntryFieldDraft, EntryFieldId, EntryFieldKind,
        EntrySensitivity,
    },
    repository::SecretBytes,
};

/// Presentation state for editing one storage-owned document.
///
/// The focused buffer contains one complete storage-owned logical field.
/// Committing it delegates classification back to `EntryDocument`, so names,
/// multiline values, duplicate fields, notes, and OTP URI recognition remain
/// storage behavior.
pub struct EntryEditor {
    document: EntryDocument,
    focused: usize,
    buffer: SecretBytes,
    cursor: usize,
    input_active: bool,
    dirty: bool,
}

impl EntryEditor {
    pub fn new(document: EntryDocument) -> Self {
        let buffer = document.fields().first().map_or_else(
            || SecretBytes::new(Vec::new()),
            |field| SecretBytes::new(field.contents().expose().to_vec()),
        );
        let cursor = buffer.expose().len();
        Self {
            document,
            focused: 0,
            buffer,
            cursor,
            input_active: false,
            dirty: false,
        }
    }

    pub fn document(&self) -> &EntryDocument {
        &self.document
    }

    pub fn focused_index(&self) -> Option<usize> {
        (!self.document.fields().is_empty()).then_some(self.focused)
    }

    pub fn focused_field(&self) -> Option<&EntryField> {
        self.document.fields().get(self.focused)
    }

    pub fn focused_contents(&self, id: EntryFieldId) -> Option<&[u8]> {
        self.focused_field()
            .filter(|field| field.id() == id)
            .map(|_| self.buffer.expose())
    }

    pub fn cursor(&self) -> usize {
        self.cursor
    }

    pub fn is_input_active(&self) -> bool {
        self.input_active
    }

    pub fn begin_input(&mut self) {
        if self.focused_field().is_some() {
            self.input_active = true;
        }
    }

    pub fn end_input(&mut self) {
        self.input_active = false;
    }

    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    pub fn focus_next(&mut self) -> Result<(), DocumentError> {
        self.flush()?;
        if !self.document.fields().is_empty() {
            self.focused = (self.focused + 1) % self.document.fields().len();
            self.load_focused();
        }
        Ok(())
    }

    pub fn focus_previous(&mut self) -> Result<(), DocumentError> {
        self.flush()?;
        if !self.document.fields().is_empty() {
            self.focused = self
                .focused
                .checked_sub(1)
                .unwrap_or(self.document.fields().len() - 1);
            self.load_focused();
        }
        Ok(())
    }

    pub fn add_after_focused(&mut self) -> Result<(), DocumentError> {
        self.flush()?;
        let index = if self.document.fields().is_empty() {
            0
        } else {
            self.focused + 1
        };
        self.document.add(index, EntryFieldDraft::blank())?;
        self.focused = index;
        self.dirty = true;
        self.load_focused();
        self.input_active = true;
        Ok(())
    }

    pub fn remove_focused(&mut self) -> Result<(), DocumentError> {
        self.flush()?;
        let Some(field) = self.focused_field() else {
            return Ok(());
        };
        self.document.remove(field.id())?;
        self.focused = self
            .focused
            .min(self.document.fields().len().saturating_sub(1));
        self.dirty = true;
        self.load_focused();
        Ok(())
    }

    pub fn move_focused_up(&mut self) -> Result<(), DocumentError> {
        self.flush()?;
        if self.focused == 0 {
            return Ok(());
        }
        let id = self
            .focused_field()
            .expect("nonzero focus has a field")
            .id();
        self.focused -= 1;
        self.document.reorder(id, self.focused)?;
        self.dirty = true;
        self.load_focused();
        Ok(())
    }

    pub fn move_focused_down(&mut self) -> Result<(), DocumentError> {
        self.flush()?;
        if self.focused + 1 >= self.document.fields().len() {
            return Ok(());
        }
        let id = self.focused_field().expect("focus has a field").id();
        self.focused += 1;
        self.document.reorder(id, self.focused)?;
        self.dirty = true;
        self.load_focused();
        Ok(())
    }

    pub fn generation_target(&mut self) -> Result<Option<EntryFieldId>, DocumentError> {
        self.flush()?;
        Ok(self.focused_field().and_then(|field| {
            (field.metadata().sensitivity() == EntrySensitivity::Sensitive
                && field.metadata().kind() != EntryFieldKind::OtpUri)
                .then_some(field.id())
        }))
    }

    pub fn apply_generated(
        &mut self,
        target: EntryFieldId,
        password: SecretBytes,
    ) -> Result<(), DocumentError> {
        self.flush()?;
        self.document
            .replace_field_value(target, password.expose().to_vec())?;
        self.dirty = true;
        if self
            .focused_field()
            .is_some_and(|field| field.id() == target)
        {
            self.load_focused();
        }
        Ok(())
    }

    pub fn prepare_save(mut self) -> Result<Self, Box<(Self, DocumentError)>> {
        match self.flush() {
            Ok(()) => Ok(self),
            Err(error) => Err(Box::new((self, error))),
        }
    }

    pub fn into_document(mut self) -> Result<EntryDocument, Box<(Self, DocumentError)>> {
        match self.flush() {
            Ok(()) => Ok(self.document),
            Err(error) => Err(Box::new((self, error))),
        }
    }

    pub fn insert_character(&mut self, character: char) {
        if !self.input_active || self.focused_field().is_none() || character.is_control() {
            return;
        }
        let encoded = character.to_string();
        let mut replacement = Vec::with_capacity(self.buffer.expose().len() + encoded.len());
        replacement.extend_from_slice(&self.buffer.expose()[..self.cursor]);
        replacement.extend_from_slice(encoded.as_bytes());
        replacement.extend_from_slice(&self.buffer.expose()[self.cursor..]);
        self.cursor += encoded.len();
        self.replace_buffer(replacement);
    }

    pub fn backspace(&mut self) {
        if !self.input_active || self.cursor == 0 {
            return;
        }
        let start = previous_character_boundary(self.buffer.expose(), self.cursor);
        let mut replacement =
            Vec::with_capacity(self.buffer.expose().len() - (self.cursor - start));
        replacement.extend_from_slice(&self.buffer.expose()[..start]);
        replacement.extend_from_slice(&self.buffer.expose()[self.cursor..]);
        self.cursor = start;
        self.replace_buffer(replacement);
    }

    pub fn delete(&mut self) {
        if !self.input_active || self.cursor >= self.buffer.expose().len() {
            return;
        }
        let end = next_character_boundary(self.buffer.expose(), self.cursor);
        let mut replacement = Vec::with_capacity(self.buffer.expose().len() - (end - self.cursor));
        replacement.extend_from_slice(&self.buffer.expose()[..self.cursor]);
        replacement.extend_from_slice(&self.buffer.expose()[end..]);
        self.replace_buffer(replacement);
    }

    pub fn move_cursor_left(&mut self) {
        self.cursor = previous_character_boundary(self.buffer.expose(), self.cursor);
    }

    pub fn move_cursor_right(&mut self) {
        self.cursor = next_character_boundary(self.buffer.expose(), self.cursor);
    }

    pub fn move_cursor_home(&mut self) {
        self.cursor = 0;
    }

    pub fn move_cursor_end(&mut self) {
        self.cursor = self.buffer.expose().len();
    }

    pub fn split_line(&mut self) -> Result<(), DocumentError> {
        if !self.input_active {
            return Ok(());
        }
        let Some(field) = self.focused_field() else {
            return Ok(());
        };
        if matches!(
            field.metadata().kind(),
            EntryFieldKind::Username
                | EntryFieldKind::Email
                | EntryFieldKind::Url
                | EntryFieldKind::Field
                | EntryFieldKind::Note
        ) {
            let mut replacement = Vec::with_capacity(self.buffer.expose().len() + 1);
            replacement.extend_from_slice(&self.buffer.expose()[..self.cursor]);
            replacement.push(b'\n');
            replacement.extend_from_slice(&self.buffer.expose()[self.cursor..]);
            self.cursor += 1;
            self.replace_buffer(replacement);
            return Ok(());
        }
        let id = field.id();
        let left = self.buffer.expose()[..self.cursor].to_vec();
        let right = self.buffer.expose()[self.cursor..].to_vec();
        self.document.update(id, EntryFieldDraft::line(left)?)?;
        self.document
            .add(self.focused + 1, EntryFieldDraft::line(right)?)?;
        self.focused += 1;
        self.dirty = true;
        self.load_focused();
        Ok(())
    }

    fn flush(&mut self) -> Result<(), DocumentError> {
        let Some(field) = self.focused_field() else {
            return Ok(());
        };
        if field.contents().expose() == self.buffer.expose() {
            return Ok(());
        }
        let id = field.id();
        self.document.update(
            id,
            EntryFieldDraft::multiline(self.buffer.expose().to_vec()),
        )?;
        self.dirty = true;
        Ok(())
    }

    fn load_focused(&mut self) {
        self.buffer = self.focused_field().map_or_else(
            || SecretBytes::new(Vec::new()),
            |field| SecretBytes::new(field.contents().expose().to_vec()),
        );
        self.cursor = self.buffer.expose().len();
        if self.focused_field().is_none() {
            self.input_active = false;
        }
    }

    fn replace_buffer(&mut self, replacement: Vec<u8>) {
        self.buffer = SecretBytes::new(replacement);
        let Some(id) = self.focused_field().map(EntryField::id) else {
            return;
        };
        let draft = EntryFieldDraft::multiline(self.buffer.expose().to_vec());
        self.document
            .update(id, draft)
            .expect("the focused field identifier belongs to the document");
        self.dirty = true;
    }
}

impl fmt::Debug for EntryEditor {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("EntryEditor")
            .field("document", &self.document)
            .field("focused", &self.focused)
            .field("buffer", &"[REDACTED]")
            .field("cursor", &self.cursor)
            .field("input_active", &self.input_active)
            .field("dirty", &self.dirty)
            .finish()
    }
}

fn previous_character_boundary(value: &[u8], cursor: usize) -> usize {
    let mut boundary = cursor.saturating_sub(1);
    while boundary > 0 && value[boundary].is_ascii_continuation() {
        boundary -= 1;
    }
    boundary
}

fn next_character_boundary(value: &[u8], cursor: usize) -> usize {
    let mut boundary = cursor.saturating_add(1).min(value.len());
    while boundary < value.len() && value[boundary].is_ascii_continuation() {
        boundary += 1;
    }
    boundary
}

trait Utf8Continuation {
    fn is_ascii_continuation(&self) -> bool;
}

impl Utf8Continuation for u8 {
    fn is_ascii_continuation(&self) -> bool {
        self & 0b1100_0000 == 0b1000_0000
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::viewer::test_support::fixture_document;

    #[test]
    fn unicode_input_and_focus_are_predictable() {
        let mut editor = EntryEditor::new(fixture_document("unicode/咖啡"));
        editor.begin_input();
        editor.move_cursor_end();
        editor.insert_character('界');
        editor.focus_next().expect("focus next");
        editor.focus_previous().expect("focus previous");
        assert!(
            editor
                .focused_field()
                .expect("password")
                .contents()
                .expose()
                .ends_with("界".as_bytes())
        );
    }

    #[test]
    fn add_multiline_remove_and_reorder_keep_stable_focus() {
        let mut editor = EntryEditor::new(fixture_document("email/personal"));
        let initial = editor.document().fields().len();
        editor.add_after_focused().expect("add");
        assert_eq!(editor.focused_index(), Some(1));
        editor.insert_character('a');
        editor.insert_character('b');
        editor.move_cursor_left();
        editor.split_line().expect("split");
        assert_eq!(editor.document().fields().len(), initial + 1);
        assert_eq!(
            editor.focused_contents(editor.focused_field().expect("field").id()),
            Some(b"a\nb".as_slice())
        );
        editor.move_focused_up().expect("move up");
        assert_eq!(editor.focused_index(), Some(0));
        editor.remove_focused().expect("remove");
        assert_eq!(editor.document().fields().len(), initial);
    }

    #[test]
    fn generated_values_preserve_structured_names_and_empty_documents_are_editable() {
        let mut editor = EntryEditor::new(fixture_document("email/personal"));
        editor.focus_next().expect("username");
        assert_eq!(editor.generation_target().expect("target"), None);
        editor.focus_previous().expect("password");
        let target = editor
            .generation_target()
            .expect("target")
            .expect("sensitive target");
        editor
            .apply_generated(target, SecretBytes::new(b"generated".to_vec()))
            .expect("apply generated");
        assert_eq!(
            editor.focused_field().expect("password").value(),
            b"generated"
        );

        let mut empty = EntryEditor::new(fixture_document("new/empty-document"));
        assert_eq!(empty.focused_index(), None);
        empty.add_after_focused().expect("add first field");
        assert_eq!(empty.focused_index(), Some(0));

        let mut otp = EntryEditor::new(fixture_document("otp/totp"));
        otp.focus_next().expect("OTP URI");
        assert_eq!(otp.generation_target().expect("OTP target"), None);
    }

    #[test]
    fn duplicate_labels_and_arbitrary_reordering_round_trip_without_schema_assumptions() {
        let mut document = fixture_document("new/duplicate-document");
        document
            .add(
                0,
                EntryFieldDraft::line(b"password".to_vec()).expect("password"),
            )
            .expect("add password");
        document
            .add(
                1,
                EntryFieldDraft::field("custom", b"one".to_vec()).expect("field"),
            )
            .expect("add first duplicate");
        document
            .add(
                2,
                EntryFieldDraft::field("custom", b"two".to_vec()).expect("field"),
            )
            .expect("add second duplicate");
        let mut editor = EntryEditor::new(document);
        editor.focus_next().expect("first duplicate");
        editor.focus_next().expect("second duplicate");
        editor.move_focused_up().expect("reorder duplicate");
        let serialized = editor.into_document().expect("valid editor").serialize();
        assert_eq!(serialized.expose(), b"password\ncustom: two\ncustom: one");
    }
}
