use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
use ratatui::widgets::ListState;

use crate::ldap::ChildNode;
use crate::model::{EditState, TextInput};
use crate::schema::PickerEntry;

// ── Mode ──────────────────────────────────────────────────────────────────────

/// アプリの主状態機械。各モードが必要とする状態を payload として保持する。
/// data-less 変種は「現在表示しているビュー」だけを示し、状態は App 直下の
/// 永続フィールド（search / pending_must_queue 等）を参照する。
pub enum Mode {
    /// ツリー＋詳細の通常表示
    Browse,
    /// `/` で開いた検索フィルタ入力中。文字列は `App.search.input` を共用。
    SearchInput,
    /// 検索ヒット一覧表示中。データは `App.search.results / list_state` を参照。
    SearchResults,
    /// 複数 namingContexts から起動時に選択
    SelectSuffix(SuffixSelection),
    /// 詳細ペインの行で `Enter` を押した時の e/a/d オーバーレイ
    ActionDialog,
    /// 既存属性値の編集
    EditValue(EditState),
    /// 新規属性値の追加
    AddValue(EditState),
    /// 手入力で属性名を入力中（ピッカー未使用時）
    AddAttrName(EditState),
    /// 属性値削除の y/N 確認
    ConfirmDelete(EditState),
    /// 100 件超の子展開時の y/N 確認
    ConfirmExpand(PendingExpansion),
    /// 属性追加ピッカー
    Picker(PickerState),
    /// objectClass 追加ピッカー
    OcPicker(PickerState),
    /// objectClass 削除確認（孤立属性一覧付き）
    ConfirmOcDelete(OcDeleteState),
    /// ツリーで選択中のエントリ削除確認 (y/N)
    ConfirmEntryDelete(EntryDeleteState),
    /// ツリーで選択中エントリの直下に新エントリを作成するウィザード
    CreateChild(CreateChildState),
}

impl Mode {
    /// 表示中の入力ステート（編集系モード）への可変参照。
    pub fn edit_state_mut(&mut self) -> Option<&mut EditState> {
        match self {
            Mode::EditValue(es)
            | Mode::AddValue(es)
            | Mode::AddAttrName(es)
            | Mode::ConfirmDelete(es) => Some(es),
            _ => None,
        }
    }
}

// ── SuffixSelection ───────────────────────────────────────────────────────────

pub struct SuffixSelection {
    pub candidates: Vec<String>,
    pub state: ListState,
}

impl SuffixSelection {
    pub fn new(candidates: Vec<String>) -> Self {
        let mut state = ListState::default();
        if !candidates.is_empty() {
            state.select(Some(0));
        }
        Self { candidates, state }
    }

    pub fn up(&mut self) {
        let i = self
            .state
            .selected()
            .map(|i| i.saturating_sub(1))
            .unwrap_or(0);
        self.state.select(Some(i));
    }

    pub fn down(&mut self) {
        let max = self.candidates.len().saturating_sub(1);
        let i = self.state.selected().map(|i| (i + 1).min(max)).unwrap_or(0);
        self.state.select(Some(i));
    }

    pub fn selected(&self) -> Option<&String> {
        self.candidates.get(self.state.selected()?)
    }
}

// ── PendingExpansion ──────────────────────────────────────────────────────────

/// 多件数展開の y/N 確認中に保留する取得済み子リスト
pub struct PendingExpansion {
    pub dn: String,
    pub children: Vec<ChildNode>,
}

// ── OcDeleteState ─────────────────────────────────────────────────────────────

/// objectClass 削除確認ダイアログに必要な状態
pub struct OcDeleteState {
    pub oc_name: String,
    /// 削除対象 OC が管理している属性の (attr, value) ペア（複数値は複数行）
    pub orphaned: Vec<(String, String)>,
}

// ── EntryDeleteState ──────────────────────────────────────────────────────────

/// ツリー上のエントリ削除確認ダイアログに必要な状態
pub struct EntryDeleteState {
    pub dn: String,
    /// `hasSubordinates=TRUE` のエントリは LDAP サーバが NotAllowedOnNonLeaf で
    /// 拒否する。確認画面に注意書きを出すための情報。
    pub has_children: bool,
}

// ── CreateChildState ──────────────────────────────────────────────────────────

/// ツリー pane で `a` を押したときの「子エントリ作成」ウィザード状態。
/// PickOc → Form の 2 フェーズを 1 つのウィンドウで遷移させる。
pub struct CreateChildState {
    pub parent_dn: String,
    pub phase: CreatePhase,
}

pub enum CreatePhase {
    /// STRUCTURAL OC 選択（ピッカー）
    PickOc(PickerState),
    /// 新エントリの RDN として使う属性を MUST 属性リストから選ぶ
    PickRdn(RdnPickState),
    /// MUST 属性入力フォーム
    Form(FormState),
}

/// RDN 属性選択フェーズの状態。
/// MUST ∪ MAY (objectClass / 運用属性を除く) から fuzzy フィルタ付きで 1 つ選ぶ。
/// `is_must` バッジで MUST/MAY を区別表示する。
pub struct RdnPickState {
    pub oc_name: String,
    /// LDAP add 時に objectClass 値として送る OC 名のリスト（SUP チェーン込み）。
    /// Form フェーズに引き継ぐため保持する。
    pub oc_chain: Vec<String>,
    pub picker: PickerState,
}

/// 入力フォームのステート。各 MUST 属性に対する TextInput を保持する。
pub struct FormState {
    pub oc_name: String,
    /// LDAP add 時に objectClass 値として送る OC 名のリスト（SUP チェーン込み）
    pub oc_chain: Vec<String>,
    pub fields: Vec<FormField>,
    pub focus: FormFocus,
    /// `fields` 上で RDN として使う属性のインデックス
    pub rdn_idx: usize,
}

pub struct FormField {
    pub attr: String,
    pub input: TextInput,
}

#[derive(Clone, Copy, PartialEq)]
pub enum FormFocus {
    Field(usize),
    Submit,
    Cancel,
}

impl FormState {
    /// Tab / ↓ で次の focus へ移動する。フィールド → Submit → Cancel → 先頭フィールド の循環。
    pub fn focus_next(&mut self) {
        self.focus = match self.focus {
            FormFocus::Field(i) if i + 1 < self.fields.len() => FormFocus::Field(i + 1),
            FormFocus::Field(_) => FormFocus::Submit,
            FormFocus::Submit => FormFocus::Cancel,
            FormFocus::Cancel => FormFocus::Field(0),
        };
    }

    /// Shift+Tab / ↑ で前の focus へ。
    pub fn focus_prev(&mut self) {
        self.focus = match self.focus {
            FormFocus::Field(0) => FormFocus::Cancel,
            FormFocus::Field(i) => FormFocus::Field(i - 1),
            FormFocus::Submit => FormFocus::Field(self.fields.len().saturating_sub(1)),
            FormFocus::Cancel => FormFocus::Submit,
        };
    }

    /// 現在 focus 中の TextInput への可変参照
    pub fn focused_input_mut(&mut self) -> Option<&mut TextInput> {
        match self.focus {
            FormFocus::Field(i) => self.fields.get_mut(i).map(|f| &mut f.input),
            _ => None,
        }
    }

    /// DN プレビュー: rdn_attr=rdn_value,parent_dn
    pub fn dn_preview(&self, parent_dn: &str) -> String {
        let rdn_attr = self
            .fields
            .get(self.rdn_idx)
            .map(|f| f.attr.as_str())
            .unwrap_or("");
        let rdn_val = self
            .fields
            .get(self.rdn_idx)
            .map(|f| f.input.text())
            .unwrap_or("");
        if rdn_attr.is_empty() {
            parent_dn.to_string()
        } else if rdn_val.is_empty() {
            format!("{rdn_attr}=<...>,{parent_dn}")
        } else {
            format!("{rdn_attr}={rdn_val},{parent_dn}")
        }
    }
}

/// RDN ピッカーの候補から初期ハイライト位置 (`PickerState::selected`) を推定する。
/// 優先順位: cn → ou → uid → dc → 先頭。慣用的な RDN 属性が候補にあれば先頭に戻すための仕組み。
pub fn pick_rdn_initial(entries: &[crate::schema::PickerEntry]) -> usize {
    for preferred in ["cn", "ou", "uid", "dc"] {
        if let Some(i) = entries
            .iter()
            .position(|e| e.attr_name.eq_ignore_ascii_case(preferred))
        {
            return i;
        }
    }
    0
}

// ── SearchState ───────────────────────────────────────────────────────────────

/// SearchInput と SearchResults をまたいで保持する検索関連状態
pub struct SearchState {
    pub input: TextInput,
    pub results: Vec<String>,
    pub list_state: ListState,
}

impl SearchState {
    pub fn new() -> Self {
        Self {
            input: TextInput::new(),
            results: Vec::new(),
            list_state: ListState::default(),
        }
    }

    pub fn set_results(&mut self, results: Vec<String>) {
        let mut st = ListState::default();
        if !results.is_empty() {
            st.select(Some(0));
        }
        self.results = results;
        self.list_state = st;
    }

    pub fn up(&mut self) {
        let i = self
            .list_state
            .selected()
            .map(|i| i.saturating_sub(1))
            .unwrap_or(0);
        self.list_state.select(Some(i));
    }

    pub fn down(&mut self) {
        let max = self.results.len().saturating_sub(1);
        let i = self
            .list_state
            .selected()
            .map(|i| (i + 1).min(max))
            .unwrap_or(0);
        self.list_state.select(Some(i));
    }

    pub fn selected_dn(&self) -> Option<String> {
        self.results.get(self.list_state.selected()?).cloned()
    }

    pub fn reset_input(&mut self) {
        self.input.clear();
    }
}

impl Default for SearchState {
    fn default() -> Self {
        Self::new()
    }
}

// ── PickerState ───────────────────────────────────────────────────────────────

pub struct PickerState {
    pub input: TextInput,
    pub entries: Vec<PickerEntry>,
    pub filtered: Vec<usize>,
    pub selected: usize,
}

impl PickerState {
    pub fn new(entries: Vec<PickerEntry>) -> Self {
        let filtered = (0..entries.len()).collect();
        Self {
            input: TextInput::new(),
            entries,
            filtered,
            selected: 0,
        }
    }

    pub fn update_filter(&mut self) {
        if self.input.is_empty() {
            self.filtered = (0..self.entries.len()).collect();
        } else {
            let matcher = SkimMatcherV2::default();
            let mut scored: Vec<(usize, i64)> = self
                .entries
                .iter()
                .enumerate()
                .filter_map(|(i, e)| {
                    matcher
                        .fuzzy_match(&e.attr_name, self.input.text())
                        .map(|s| (i, s))
                })
                .collect();
            scored.sort_by_key(|&(_, score)| std::cmp::Reverse(score));
            self.filtered = scored.into_iter().map(|(i, _)| i).collect();
        }
        if self.selected >= self.filtered.len().max(1) {
            self.selected = 0;
        }
    }

    pub fn selected_entry(&self) -> Option<&PickerEntry> {
        self.entries.get(*self.filtered.get(self.selected)?)
    }

    pub fn up(&mut self) {
        self.selected = self.selected.saturating_sub(1);
    }
    pub fn down(&mut self) {
        let max = self.filtered.len().saturating_sub(1);
        if self.selected < max {
            self.selected += 1;
        }
    }

    pub fn insert_char(&mut self, c: char) {
        self.input.insert(c);
        self.update_filter();
    }

    pub fn backspace(&mut self) {
        self.input.backspace();
        self.update_filter();
    }
}
