use crate::frame::FrameCtx;
use crate::layout::{LayoutPos, LayoutPx, LayoutRect, LayoutSize};
use crate::strings::StringKey;
use crate::theme::Step12;
use crate::widget_id::{WidgetId, WidgetKey};

use super::dialog::{Dialog, DialogButton, show_dialog};
use super::paint::{LabelText, WidgetPaint};
use super::table::{ListItem, ListView, ListViewState, show_list_view};
use super::text_input::{AlwaysValid, MemoryClipboard, TextInput, TextInputState, show_text_input};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FilePickerMode {
    Open,
    Save,
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct FilePickerEntry {
    pub id: WidgetId,
    pub label: StringKey,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct FilePickerState {
    pub list: ListViewState,
    pub filename: TextInputState,
    pub clipboard: MemoryClipboard,
}

#[derive(Debug, PartialEq)]
pub struct FilePickerDialog<'a, 'state> {
    pub id: WidgetId,
    pub viewport: LayoutRect,
    pub size: LayoutSize,
    pub mode: FilePickerMode,
    pub current_path: StringKey,
    pub entries: &'a [FilePickerEntry],
    pub state: &'state mut FilePickerState,
    pub title: StringKey,
}

impl<'a, 'state> FilePickerDialog<'a, 'state> {
    #[must_use]
    pub fn new(
        id: WidgetId,
        viewport: LayoutRect,
        mode: FilePickerMode,
        current_path: StringKey,
        entries: &'a [FilePickerEntry],
        title: StringKey,
        state: &'state mut FilePickerState,
    ) -> Self {
        Self {
            id,
            viewport,
            size: LayoutSize::new(LayoutPx::new(540.0), LayoutPx::new(420.0)),
            mode,
            current_path,
            entries,
            state,
            title,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum FilePickerOutcome {
    Cancelled,
    Open {
        folder: WidgetId,
    },
    Save {
        folder: Option<WidgetId>,
        filename: String,
    },
}

#[derive(Clone, Debug, PartialEq)]
pub struct FilePickerResponse {
    pub outcome: Option<FilePickerOutcome>,
    pub navigated_to: Option<WidgetId>,
    pub paint: Vec<WidgetPaint>,
}

#[must_use]
pub fn show_file_picker(
    ctx: &mut FrameCtx<'_>,
    picker: FilePickerDialog<'_, '_>,
) -> FilePickerResponse {
    let FilePickerDialog {
        id,
        viewport,
        size,
        mode,
        current_path,
        entries,
        state,
        title,
    } = picker;
    let confirm_id = id.child(WidgetKey::new("confirm"));
    let cancel_id = id.child(WidgetKey::new("cancel"));
    let confirm_label = match mode {
        FilePickerMode::Open => StringKey::new("file_picker.open"),
        FilePickerMode::Save => StringKey::new("file_picker.save"),
    };
    let buttons = [
        DialogButton::secondary(cancel_id, StringKey::new("file_picker.cancel")),
        DialogButton::primary(confirm_id, confirm_label),
    ];
    let list_items: Vec<ListItem> = entries
        .iter()
        .map(|e| ListItem {
            id: e.id,
            label: e.label,
        })
        .collect();
    let (response, navigated_to) = show_dialog(
        ctx,
        Dialog::new(id, viewport, size, title, &buttons),
        |ctx, body_rect, paint| {
            paint.push(WidgetPaint::Label {
                rect: path_label_rect(body_rect),
                text: LabelText::Key(current_path),
                color: ctx.theme().colors.text_secondary(),
                role: ctx.theme().typography.caption,
            });
            let list_rect = list_rect_for(body_rect, mode);
            paint.push(WidgetPaint::Surface {
                rect: list_rect,
                fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0),
                border: Some(crate::theme::Border {
                    width: crate::theme::StrokeWidth::HAIRLINE,
                    color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER),
                }),
                radius: ctx.theme().radius.sm,
                elevation: None,
            });
            let list_response = show_list_view(
                ctx,
                ListView::new(
                    id.child(WidgetKey::new("list")),
                    list_rect,
                    StringKey::new("file_picker.list"),
                    &list_items,
                    &mut state.list,
                ),
            );
            paint.extend(list_response.paint);
            if mode == FilePickerMode::Save {
                let widget = TextInput {
                    id: id.child(WidgetKey::new("filename")),
                    rect: filename_rect(body_rect),
                    placeholder: StringKey::new("file_picker.filename_placeholder"),
                    state: &mut state.filename,
                    disabled: false,
                    validator: AlwaysValid,
                };
                let text_response = show_text_input(ctx, widget, &mut state.clipboard);
                paint.extend(text_response.paint);
            }
            list_response.activated
        },
    );
    let outcome = if response.dismissed || response.activated == Some(cancel_id) {
        Some(FilePickerOutcome::Cancelled)
    } else if response.activated == Some(confirm_id) {
        match mode {
            FilePickerMode::Open => navigated_to
                .or(state.list.focused)
                .map(|folder| FilePickerOutcome::Open { folder }),
            FilePickerMode::Save => Some(FilePickerOutcome::Save {
                folder: state.list.focused,
                filename: state.filename.text.clone(),
            }),
        }
    } else {
        None
    };
    FilePickerResponse {
        outcome,
        navigated_to,
        paint: response.paint,
    }
}

fn path_label_rect(body: LayoutRect) -> LayoutRect {
    LayoutRect::new(
        LayoutPos::new(
            LayoutPx::new(body.origin.x.value() + 16.0),
            LayoutPx::new(body.origin.y.value() + 8.0),
        ),
        LayoutSize::new(
            LayoutPx::saturating_nonneg(body.size.width.value() - 32.0),
            LayoutPx::new(20.0),
        ),
    )
}

fn list_rect_for(body: LayoutRect, mode: FilePickerMode) -> LayoutRect {
    let list_height = match mode {
        FilePickerMode::Open => body.size.height.value() - 36.0,
        FilePickerMode::Save => body.size.height.value() - 80.0,
    }
    .max(0.0);
    LayoutRect::new(
        LayoutPos::new(
            LayoutPx::new(body.origin.x.value() + 16.0),
            LayoutPx::new(body.origin.y.value() + 32.0),
        ),
        LayoutSize::new(
            LayoutPx::saturating_nonneg(body.size.width.value() - 32.0),
            LayoutPx::new(list_height),
        ),
    )
}

fn filename_rect(body: LayoutRect) -> LayoutRect {
    LayoutRect::new(
        LayoutPos::new(
            LayoutPx::new(body.origin.x.value() + 16.0),
            LayoutPx::new(body.origin.y.value() + body.size.height.value() - 36.0),
        ),
        LayoutSize::new(
            LayoutPx::saturating_nonneg(body.size.width.value() - 32.0),
            LayoutPx::new(28.0),
        ),
    )
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use super::{
        FilePickerDialog, FilePickerEntry, FilePickerMode, FilePickerOutcome, FilePickerState,
        show_file_picker,
    };
    use crate::focus::FocusManager;
    use crate::frame::FrameCtx;
    use crate::hit_test::{HitFrame, HitState, resolve};
    use crate::hotkey::HotkeyTable;
    use crate::input::{FrameInstant, InputSnapshot, KeyCode, KeyEvent, ModifierMask, NamedKey};
    use crate::layout::{LayoutPos, LayoutPx, LayoutRect, LayoutSize};
    use crate::strings::{StringKey, StringTable};
    use crate::theme::Theme;
    use crate::widget_id::{WidgetId, WidgetKey};

    fn picker_id() -> WidgetId {
        WidgetId::ROOT.child(WidgetKey::new("picker"))
    }

    fn entries() -> Vec<FilePickerEntry> {
        vec![
            FilePickerEntry {
                id: picker_id().child(WidgetKey::new("doc1")),
                label: StringKey::new("doc.first"),
            },
            FilePickerEntry {
                id: picker_id().child(WidgetKey::new("doc2")),
                label: StringKey::new("doc.second"),
            },
        ]
    }

    fn viewport() -> LayoutRect {
        LayoutRect::new(
            LayoutPos::new(LayoutPx::ZERO, LayoutPx::ZERO),
            LayoutSize::new(LayoutPx::new(800.0), LayoutPx::new(600.0)),
        )
    }

    #[test]
    fn escape_cancels_picker() {
        let entries = entries();
        let mut state = FilePickerState::default();
        let theme = Arc::new(Theme::light());
        let table = HotkeyTable::new();
        let mut focus = FocusManager::new();
        let mut hits = HitFrame::new();
        let prev = HitState::new();
        let mut snap = InputSnapshot::idle(FrameInstant::ZERO);
        snap.keys_pressed.push(KeyEvent::new(
            KeyCode::Named(NamedKey::Escape),
            ModifierMask::NONE,
        ));
        let response = {
            let mut shaper = bone_text::Shaper::new();
            let mut a11y = crate::a11y::AccessTreeBuilder::new();
            let mut ctx = FrameCtx::new(
                theme,
                &mut snap,
                &mut focus,
                &table,
                StringTable::empty(),
                &mut hits,
                &prev,
                &mut a11y,
                &mut shaper,
            );
            show_file_picker(
                &mut ctx,
                FilePickerDialog::new(
                    picker_id(),
                    viewport(),
                    FilePickerMode::Open,
                    StringKey::new("path.home"),
                    &entries,
                    StringKey::new("file_picker.title"),
                    &mut state,
                ),
            )
        };
        assert_eq!(response.outcome, Some(FilePickerOutcome::Cancelled));
    }

    #[test]
    fn save_mode_paints_filename_input_above_buttons() {
        let entries = entries();
        let mut state = FilePickerState::default();
        let theme = Arc::new(Theme::light());
        let table = HotkeyTable::new();
        let mut focus = FocusManager::new();
        let mut hits = HitFrame::new();
        let prev = HitState::new();
        let mut snap = InputSnapshot::idle(FrameInstant::ZERO);
        let response = {
            let mut shaper = bone_text::Shaper::new();
            let mut a11y = crate::a11y::AccessTreeBuilder::new();
            let mut ctx = FrameCtx::new(
                theme,
                &mut snap,
                &mut focus,
                &table,
                StringTable::empty(),
                &mut hits,
                &prev,
                &mut a11y,
                &mut shaper,
            );
            show_file_picker(
                &mut ctx,
                FilePickerDialog::new(
                    picker_id(),
                    viewport(),
                    FilePickerMode::Save,
                    StringKey::new("path.home"),
                    &entries,
                    StringKey::new("file_picker.title"),
                    &mut state,
                ),
            )
        };
        let _next = resolve(&prev, &hits, &snap, focus.focused());
        let label_count = response
            .paint
            .iter()
            .filter(|p| matches!(p, super::WidgetPaint::Label { .. }))
            .count();
        assert!(label_count >= 2);
    }
}
