//! 純粋なドメインデータ型。Mode はアプリ状態機械のため `app::state` に置く。

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

// ── Pane ──────────────────────────────────────────────────────────────────────

#[derive(PartialEq)]
pub enum Pane {
    Tree,
    Detail,
}

// ── DitNode ───────────────────────────────────────────────────────────────────

/// DIT の1ノード。木構造で LDAP ディレクトリ階層を保持する。
///
/// - `children: None`     → 未ロード
/// - `children: Some([])` → ロード済みだが子なし
/// - `children: Some([…])` → キャッシュ済み（collapsed でも保持）
pub struct DitNode {
    pub dn: String,
    pub has_children: bool,
    pub expanded: bool,
    pub children: Option<Vec<DitNode>>,
}

impl DitNode {
    /// 未ロードの子あり候補
    pub fn unloaded(dn: String, has_children: bool) -> Self {
        Self {
            dn,
            has_children,
            expanded: false,
            children: None,
        }
    }

    /// DN で再帰検索し可変参照を返す
    pub fn find_mut(&mut self, dn: &str) -> Option<&mut Self> {
        if self.dn == dn {
            return Some(self);
        }
        if let Some(children) = &mut self.children {
            for child in children.iter_mut() {
                if let Some(found) = child.find_mut(dn) {
                    return Some(found);
                }
            }
        }
        None
    }

    /// 折りたたみ（children はキャッシュとして保持）
    pub fn collapse(&mut self) {
        self.expanded = false;
    }

    /// 子を差し替えて展開状態にする
    pub fn set_children(&mut self, children: Vec<DitNode>) {
        self.children = Some(children);
        self.expanded = true;
    }

    /// 子なしと確定させる
    pub fn mark_no_children(&mut self) {
        self.has_children = false;
        self.children = Some(vec![]);
    }
}

// ── FlatEntry / flat_view ─────────────────────────────────────────────────────

/// ratatui 描画のために DFS で生成するフラットエントリ
pub struct FlatEntry<'a> {
    pub node: &'a DitNode,
    pub depth: usize,
    pub is_last: bool,
    /// continuing[i] = true → 深さ i の祖先がまだ兄弟を持つ → ┆ を描画
    pub continuing: Vec<bool>,
}

/// 展開済みノードを DFS でフラットリスト化する（描画・カーソル計算用）
pub fn flat_view(root: &DitNode) -> Vec<FlatEntry<'_>> {
    let mut result = Vec::new();
    collect_flat(root, 0, true, vec![], &mut result);
    result
}

fn collect_flat<'a>(
    node: &'a DitNode,
    depth: usize,
    is_last: bool,
    continuing: Vec<bool>,
    result: &mut Vec<FlatEntry<'a>>,
) {
    result.push(FlatEntry {
        node,
        depth,
        is_last,
        continuing: continuing.clone(),
    });
    if node.expanded {
        if let Some(children) = &node.children {
            let n = children.len();
            for (i, child) in children.iter().enumerate() {
                let child_is_last = i == n - 1;
                let mut child_cont = continuing.clone();
                child_cont.push(!is_last); // 現ノードが兄弟を持つなら子は ┆ を表示
                collect_flat(child, depth + 1, child_is_last, child_cont, result);
            }
        }
    }
}

// ── TextInput ─────────────────────────────────────────────────────────────────

/// テキスト + char ベースカーソルの 1 行入力モデル。
/// 検索フィルタ・属性値編集・ピッカーフィルタで共通利用する。
#[derive(Default, Clone)]
pub struct TextInput {
    text: String,
    cursor: usize, // char 単位
}

impl TextInput {
    pub fn new() -> Self {
        Self::default()
    }

    /// text の末尾にカーソルを置いた状態で初期化
    pub fn with_text(text: String) -> Self {
        let cursor = text.chars().count();
        Self { text, cursor }
    }

    pub fn text(&self) -> &str {
        &self.text
    }
    pub fn cursor(&self) -> usize {
        self.cursor
    }
    pub fn len(&self) -> usize {
        self.text.chars().count()
    }
    pub fn is_empty(&self) -> bool {
        self.text.is_empty()
    }

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

    pub fn left(&mut self) {
        self.cursor = self.cursor.saturating_sub(1);
    }
    pub fn right(&mut self) {
        let m = self.len();
        if self.cursor < m {
            self.cursor += 1;
        }
    }
    pub fn home(&mut self) {
        self.cursor = 0;
    }
    pub fn end(&mut self) {
        self.cursor = self.len();
    }

    pub fn insert(&mut self, c: char) {
        let mut cs: Vec<char> = self.text.chars().collect();
        cs.insert(self.cursor, c);
        self.text = cs.into_iter().collect();
        self.cursor += 1;
    }

    pub fn backspace(&mut self) {
        if self.cursor > 0 {
            let mut cs: Vec<char> = self.text.chars().collect();
            cs.remove(self.cursor - 1);
            self.text = cs.into_iter().collect();
            self.cursor -= 1;
        }
    }

    pub fn delete_fwd(&mut self) {
        if self.cursor < self.len() {
            let mut cs: Vec<char> = self.text.chars().collect();
            cs.remove(self.cursor);
            self.text = cs.into_iter().collect();
        }
    }

    /// 標準的な編集キー（Left/Right/Home/End/Backspace/Delete/Char）を処理する。
    /// 消費した場合 true、未対応キーの場合 false。
    pub fn handle_key(&mut self, key: KeyEvent) -> bool {
        match key.code {
            KeyCode::Left => {
                self.left();
                true
            }
            KeyCode::Right => {
                self.right();
                true
            }
            KeyCode::Home => {
                self.home();
                true
            }
            KeyCode::End => {
                self.end();
                true
            }
            KeyCode::Backspace => {
                self.backspace();
                true
            }
            KeyCode::Delete => {
                self.delete_fwd();
                true
            }
            KeyCode::Char(c)
                if key.modifiers == KeyModifiers::NONE || key.modifiers == KeyModifiers::SHIFT =>
            {
                self.insert(c);
                true
            }
            _ => false,
        }
    }
}

// ── EditState ─────────────────────────────────────────────────────────────────

pub struct EditState {
    pub attr: String,
    pub old_value: String,
    pub input: TextInput,
}

impl EditState {
    /// 既存値の編集（input に現在値をプリセット）
    pub fn edit(attr: String, current: String) -> Self {
        Self {
            attr,
            old_value: current.clone(),
            input: TextInput::with_text(current),
        }
    }

    /// 新規値の追加（input は空）
    pub fn add(attr: String) -> Self {
        Self {
            attr,
            old_value: String::new(),
            input: TextInput::new(),
        }
    }

    /// 既存値の削除（old_value=現在値、input は使わない）
    pub fn delete(attr: String, current: String) -> Self {
        Self {
            attr,
            old_value: current,
            input: TextInput::new(),
        }
    }

    /// 属性名の入力モード（attr/old_value は空、input に名前を入れる）
    pub fn attr_name() -> Self {
        Self {
            attr: String::new(),
            old_value: String::new(),
            input: TextInput::new(),
        }
    }
}

// ── LdapEntry ─────────────────────────────────────────────────────────────────

pub struct LdapEntry {
    pub dn: String,
    pub oc_values: Vec<String>,
    pub attr_rows: Vec<(String, String)>,
    pub op_rows: Vec<(String, String)>,
    pub attr_w: usize,
}

impl LdapEntry {
    /// 既存ユーザー属性名（lowercase）+ "objectclass" の集合。
    /// スキーマピッカーや MUST 属性キュー判定で「既に値を持っている属性」を
    /// 判別するために使う。
    pub fn existing_attr_names_lower(&self) -> std::collections::HashSet<String> {
        self.attr_rows
            .iter()
            .map(|(a, _)| a.to_lowercase())
            .chain(std::iter::once("objectclass".to_string()))
            .collect()
    }
}

// ── Selection ─────────────────────────────────────────────────────────────────

pub enum Selection<'e> {
    ObjectClass {
        #[allow(dead_code)]
        index: usize,
        value: &'e str,
    },
    OcPlusRow,
    Attr {
        attr: &'e str,
        value: &'e str,
    },
    AttrPlusRow,
    OpAttr {
        #[allow(dead_code)]
        attr: &'e str,
        #[allow(dead_code)]
        value: &'e str,
    }, // 読み取り専用
}

impl Selection<'_> {
    pub fn is_oc(&self) -> bool {
        matches!(self, Selection::ObjectClass { .. })
    }
    pub fn is_oc_plus(&self) -> bool {
        matches!(self, Selection::OcPlusRow)
    }
    pub fn is_attr_plus(&self) -> bool {
        matches!(self, Selection::AttrPlusRow)
    }
    pub fn is_op(&self) -> bool {
        matches!(self, Selection::OpAttr { .. })
    }
}

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

    fn leaf(dn: &str) -> DitNode {
        DitNode {
            dn: dn.to_string(),
            has_children: false,
            expanded: false,
            children: Some(vec![]),
        }
    }

    fn branch(dn: &str, children: Vec<DitNode>) -> DitNode {
        DitNode {
            dn: dn.to_string(),
            has_children: true,
            expanded: true,
            children: Some(children),
        }
    }

    fn collapsed(dn: &str, cached_children: Vec<DitNode>) -> DitNode {
        DitNode {
            dn: dn.to_string(),
            has_children: true,
            expanded: false,
            children: Some(cached_children),
        }
    }

    #[test]
    fn flat_view_root_only() {
        let root = leaf("dc=example,dc=com");
        let flat = flat_view(&root);
        assert_eq!(flat.len(), 1);
        assert_eq!(flat[0].node.dn, "dc=example,dc=com");
        assert_eq!(flat[0].depth, 0);
        assert!(flat[0].is_last);
        assert!(flat[0].continuing.is_empty());
    }

    #[test]
    fn flat_view_two_children_continuing() {
        // root (is_last=true) → child_cont pushed = !true = false
        //   ├─ ou=users  (depth=1, is_last=false, continuing=[false])
        //   └─ ou=groups (depth=1, is_last=true,  continuing=[false])
        let root = branch(
            "dc=example,dc=com",
            vec![
                leaf("ou=users,dc=example,dc=com"),
                leaf("ou=groups,dc=example,dc=com"),
            ],
        );
        let flat = flat_view(&root);
        assert_eq!(flat.len(), 3);

        assert_eq!(flat[1].node.dn, "ou=users,dc=example,dc=com");
        assert!(!flat[1].is_last);
        assert_eq!(flat[1].continuing, vec![false]); // root has no siblings

        assert_eq!(flat[2].node.dn, "ou=groups,dc=example,dc=com");
        assert!(flat[2].is_last);
        assert_eq!(flat[2].continuing, vec![false]);
    }

    #[test]
    fn flat_view_nested_continuing() {
        // root (is_last=true)
        //   ├─ ou=users (depth=1, is_last=false)
        //   │    └─ cn=user1 (depth=2)
        //   │         continuing = [false (root→no sib), true (ou=users has sib)]
        //   └─ ou=groups (depth=1, is_last=true)
        let root = branch(
            "dc=example,dc=com",
            vec![
                branch(
                    "ou=users,dc=example,dc=com",
                    vec![leaf("cn=user1,ou=users,dc=example,dc=com")],
                ),
                leaf("ou=groups,dc=example,dc=com"),
            ],
        );
        let flat = flat_view(&root);
        assert_eq!(flat.len(), 4);

        let user1 = &flat[2];
        assert_eq!(user1.node.dn, "cn=user1,ou=users,dc=example,dc=com");
        assert_eq!(user1.depth, 2);
        // continuing[0]=false (root is_last=true → !true=false)
        // continuing[1]=true  (ou=users is_last=false → !false=true → ┆ drawn at depth1)
        assert_eq!(user1.continuing, vec![false, true]);

        let groups = &flat[3];
        assert_eq!(groups.depth, 1);
        assert!(groups.is_last);
        assert_eq!(groups.continuing, vec![false]);
    }

    #[test]
    fn flat_view_collapsed_hides_children() {
        // 折りたたみ済みノードの子は flat_view に現れない
        let root = branch(
            "dc=example,dc=com",
            vec![collapsed(
                "ou=users,dc=example,dc=com",
                vec![leaf("cn=user1,ou=users,dc=example,dc=com")],
            )],
        );
        let flat = flat_view(&root);
        assert_eq!(flat.len(), 2, "collapsed node's children must not appear");
        assert_eq!(flat[1].node.dn, "ou=users,dc=example,dc=com");
    }

    #[test]
    fn flat_view_unloaded_children_not_expanded() {
        // children=None のノードは expanded=true でも子が出ない（未ロード）
        let root = DitNode {
            dn: "dc=example,dc=com".to_string(),
            has_children: true,
            expanded: true,
            children: None, // 未ロード
        };
        let flat = flat_view(&root);
        assert_eq!(flat.len(), 1);
    }
}
