use crate::components::keyboard;
use iced::widget::button;
use std::path::Path;

use iced::widget::text::Shaping;
use iced::widget::{Space, checkbox, column, container, image, row, scrollable, text, text_input};
use iced::{Alignment, Background, Border, Color, Element, Length, Shadow, Theme, Vector};

use bds_core::i18n::UiLocale;
use bds_core::model::Media;
use bds_core::util::paths::thumbnail_path;

use crate::app::Message;
use crate::components::inputs;
use crate::i18n::t;
use crate::views::post_editor::{EditorInsertionPoint, PostEditorMsg};

#[derive(Debug, Clone)]
pub struct InsertLinkResult {
    pub post_id: String,
    pub title: String,
    pub status: String,
    pub canonical_url: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AiEntityTarget {
    Post(String),
    Media(String),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AiSuggestionField {
    pub key: String,
    pub label: String,
    pub current_value: String,
    pub suggested_value: String,
    pub accepted: bool,
    pub locked: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LanguageTarget {
    pub code: String,
    pub name: String,
    pub flag_emoji: String,
    pub has_existing_translation: bool,
    pub existing_status: Option<String>,
}

/// Active modal state. Only one modal at a time.
#[derive(Debug, Clone)]
pub enum ModalState {
    /// Per modals.allium ConfirmDeleteModal: warning, entity name, references.
    ConfirmDelete {
        entity_name: String,
        references: Vec<String>,
        on_confirm: ConfirmAction,
    },
    /// Per modals.allium ConfirmDialog: generic confirmation.
    Confirm {
        title: String,
        message: String,
        on_confirm: ConfirmAction,
    },
    SearchIndexRepair,
    SearchIndexRebuilding {
        task_id: bds_core::engine::task::TaskId,
        cancellation_requested: bool,
    },
    FindReplace {
        query: String,
        replacement: String,
        show_replace: bool,
    },
    /// Per modals.allium InsertPostLinkModal: Ctrl+K link insertion.
    PostInsertLink {
        post_id: String,
        title: String,
        insertion_point: EditorInsertionPoint,
        results: Vec<InsertLinkResult>,
        search_query: String,
        active_tab: PostInsertLinkTab,
        external_url: String,
        external_text: String,
    },
    /// Per modals.allium InsertMediaModal: grid for inserting media.
    InsertMedia {
        post_id: String,
        title: String,
        insertion_point: EditorInsertionPoint,
        media_list: Vec<bds_core::model::Media>,
        search_query: String,
        link_only: bool,
    },
    /// Per modals.allium GalleryOverlay: full-screen media gallery.
    PostGallery {
        post_id: String,
        title: String,
        media_list: Vec<bds_core::model::Media>,
        selected_index: Option<usize>,
    },
    AISuggestions {
        target: AiEntityTarget,
        fields: Vec<AiSuggestionField>,
    },
    LanguagePicker {
        target: AiEntityTarget,
        source_language: String,
        available_targets: Vec<LanguageTarget>,
    },
    RemoteConnection {
        target: String,
        connecting: bool,
        connected: bool,
        error: Option<String>,
        projects: Vec<bds_core::model::Project>,
        selected_project_id: Option<String>,
    },
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PostInsertLinkTab {
    Internal,
    External,
}

/// What action to perform when modal is confirmed.
#[derive(Debug, Clone)]
pub enum ConfirmAction {
    RebuildSearchIndex,
    OpenExternalUrl(String),
    DeleteProject(String),
    DeletePost(String),
    DeleteMedia(String),
    DeleteScript(String),
    DeleteTemplate(String),
    ForceDeleteTemplate(String),
    DeleteTag(String),
    DeleteImport(String),
    MergeTags {
        sources: Vec<String>,
        target: String,
    },
}

// ── Modal backdrop style ──

fn backdrop_style(_theme: &Theme) -> container::Style {
    container::Style {
        background: Some(Background::Color(Color::from_rgba(0.0, 0.0, 0.0, 0.5))),
        ..container::Style::default()
    }
}

fn modal_box_style(_theme: &Theme) -> container::Style {
    container::Style {
        background: Some(Background::Color(inputs::ELEVATED_BG)),
        border: Border {
            color: inputs::BORDER_COLOR,
            width: 1.0,
            radius: 10.0.into(),
        },
        shadow: Shadow {
            color: Color::from_rgba(0.0, 0.0, 0.0, 0.4),
            offset: Vector::new(0.0, 8.0),
            blur_radius: 28.0,
        },
        ..container::Style::default()
    }
}

fn cancel_button_style(theme: &Theme, status: button::Status) -> button::Style {
    inputs::secondary_button(theme, status)
}

fn danger_button_style(theme: &Theme, status: button::Status) -> button::Style {
    inputs::danger_button(theme, status)
}

fn confirm_button_style(theme: &Theme, status: button::Status) -> button::Style {
    inputs::primary_button(theme, status)
}

fn resolve_thumbnail_file(data_dir: Option<&Path>, media: &Media) -> Option<String> {
    let data_dir = data_dir?;
    if !media.mime_type.starts_with("image/") {
        return None;
    }
    let thumb = data_dir.join(thumbnail_path(&media.id, "medium", "webp"));
    if thumb.exists() {
        Some(thumb.to_string_lossy().to_string())
    } else {
        let original = data_dir.join(&media.file_path);
        original
            .exists()
            .then(|| original.to_string_lossy().to_string())
    }
}

fn resolve_media_file(data_dir: Option<&Path>, media: &Media) -> Option<String> {
    let data_dir = data_dir?;
    let path = data_dir.join(&media.file_path);
    path.exists().then(|| path.to_string_lossy().to_string())
}

pub(crate) fn external_link_markdown(url: &str, display_text: &str) -> Option<String> {
    let trimmed_url = url.trim();
    if trimmed_url.is_empty() {
        return None;
    }
    let trimmed_text = display_text.trim();
    if trimmed_text.is_empty() {
        Some(trimmed_url.to_string())
    } else {
        Some(format!("[{trimmed_text}]({trimmed_url})"))
    }
}

#[cfg(test)]
fn gallery_step(current: usize, len: usize, delta: isize) -> usize {
    if len == 0 {
        return 0;
    }
    ((current as isize + delta).rem_euclid(len as isize)) as usize
}

/// Render the modal overlay.
pub fn view(
    state: ModalState,
    locale: UiLocale,
    data_dir: Option<&Path>,
) -> Element<'static, Message> {
    let modal_content: Element<'static, Message> = match state {
        ModalState::RemoteConnection {
            target,
            connecting,
            connected,
            error,
            projects,
            selected_project_id,
        } => {
            let target_input =
                text_input(&t(locale, "remoteConnection.targetPlaceholder"), &target)
                    .on_input(Message::RemoteTargetChanged)
                    .on_submit(Message::RemoteConnectRequested)
                    .style(inputs::field_style);
            let mut content = column![
                text(t(locale, "remoteConnection.title"))
                    .size(16)
                    .shaping(Shaping::Advanced)
                    .color(Color::WHITE),
                text(t(locale, "remoteConnection.description"))
                    .size(12)
                    .shaping(Shaping::Advanced)
                    .color(Color::from_rgb(0.70, 0.70, 0.75)),
                Space::new().height(8.0),
                text(t(locale, "remoteConnection.target"))
                    .size(12)
                    .color(Color::from_rgb(0.80, 0.80, 0.85)),
                target_input,
            ]
            .spacing(6)
            .width(Length::Fill);

            if let Some(error) = error {
                content = content.push(
                    inputs::card(text(error).size(12).color(Color::from_rgb(1.0, 0.55, 0.55)))
                        .width(Length::Fill),
                );
            }

            if !projects.is_empty() {
                content = content.push(Space::new().height(6.0));
                content = content.push(
                    text(t(locale, "remoteConnection.projects"))
                        .size(13)
                        .color(Color::WHITE),
                );
                let mut project_rows = column![].spacing(6);
                for project in projects {
                    let selected = selected_project_id.as_deref() == Some(project.id.as_str());
                    let marker = if selected { "●" } else { "○" };
                    project_rows = project_rows.push(
                        keyboard::button(
                            row![
                                text(marker).size(12),
                                text(project.name.clone()).size(13),
                                Space::new().width(Length::Fill),
                                text(project.description.unwrap_or_default())
                                    .size(11)
                                    .color(Color::from_rgb(0.65, 0.65, 0.70)),
                            ]
                            .spacing(8)
                            .align_y(Alignment::Center),
                        )
                        .on_press(Message::RemoteProjectSelected(project.id))
                        .padding([7, 10])
                        .width(Length::Fill)
                        .style(if selected {
                            inputs::primary_button
                        } else {
                            inputs::secondary_button
                        }),
                    );
                }
                content = content.push(project_rows);
            }

            let cancel = keyboard::button(text(t(locale, "remoteConnection.cancel")).size(13))
                .on_press(Message::DismissModal)
                .padding([6, 16])
                .style(cancel_button_style);
            let action = if !connected {
                let button = keyboard::button(
                    text(if connecting {
                        t(locale, "remoteConnection.connecting")
                    } else {
                        t(locale, "remoteConnection.connect")
                    })
                    .size(13),
                )
                .padding([6, 16])
                .style(confirm_button_style);
                if connecting || target.trim().is_empty() {
                    button
                } else {
                    button.on_press(Message::RemoteConnectRequested)
                }
            } else {
                let button = keyboard::button(text(t(locale, "remoteConnection.open")).size(13))
                    .padding([6, 16])
                    .style(confirm_button_style);
                if selected_project_id.is_some() {
                    button.on_press(Message::RemoteOpenProjectRequested)
                } else {
                    button
                }
            };
            content = content.push(Space::new().height(8.0)).push(
                row![cancel, Space::new().width(Length::Fill), action].align_y(Alignment::Center),
            );
            container(content.padding(20))
                .width(Length::Fixed(500.0))
                .style(modal_box_style)
                .into()
        }
        ModalState::ConfirmDelete {
            entity_name,
            references,
            on_confirm,
        } => {
            let title = text(t(locale, "modal.confirmDelete.title"))
                .size(16)
                .shaping(Shaping::Advanced)
                .color(Color::WHITE);

            let warning_icon = text("\u{26A0}")
                .size(20)
                .shaping(Shaping::Advanced)
                .color(Color::from_rgb(1.0, 0.80, 0.0));

            let entity_label = text(entity_name.clone())
                .size(14)
                .shaping(Shaping::Advanced)
                .color(Color::from_rgb(0.90, 0.90, 0.95));

            let warning_text = text(t(locale, "modal.confirmDelete.warning"))
                .size(12)
                .shaping(Shaping::Advanced)
                .color(Color::from_rgb(0.70, 0.70, 0.75));

            let mut content_col = column![
                row![warning_icon, Space::new().width(8.0), title].align_y(Alignment::Center),
                Space::new().height(12.0),
                entity_label,
                warning_text,
            ]
            .spacing(4);

            // Reference section
            if !references.is_empty() {
                content_col = content_col.push(Space::new().height(8.0));
                for r in references {
                    content_col = content_col.push(
                        text(format!("\u{2022} {r}"))
                            .size(11)
                            .shaping(Shaping::Advanced)
                            .color(Color::from_rgb(0.60, 0.60, 0.65)),
                    );
                }
            }

            let on_confirm_clone = on_confirm.clone();
            let buttons = row![
                keyboard::button(
                    text(t(locale, "modal.confirmDelete.cancel"))
                        .size(13)
                        .shaping(Shaping::Advanced)
                )
                .on_press(Message::DismissModal)
                .padding([6, 16])
                .style(cancel_button_style),
                Space::new().width(Length::Fill),
                keyboard::button(
                    text(t(locale, "modal.confirmDelete.delete"))
                        .size(13)
                        .shaping(Shaping::Advanced)
                )
                .on_press(Message::ConfirmModal(on_confirm_clone))
                .padding([6, 16])
                .style(danger_button_style),
            ];

            content_col = content_col.push(Space::new().height(16.0));
            content_col = content_col.push(buttons);

            container(content_col.padding(20))
                .width(Length::Fixed(380.0))
                .style(modal_box_style)
                .into()
        }

        ModalState::Confirm {
            title: dialog_title,
            message: dialog_message,
            on_confirm,
        } => {
            let title = text(dialog_title.clone())
                .size(16)
                .shaping(Shaping::Advanced)
                .color(Color::WHITE);

            let msg = text(dialog_message.clone())
                .size(13)
                .shaping(Shaping::Advanced)
                .color(Color::from_rgb(0.80, 0.80, 0.85));

            let on_confirm_clone = on_confirm.clone();
            let buttons = row![
                keyboard::button(
                    text(t(locale, "modal.confirm.cancel"))
                        .size(13)
                        .shaping(Shaping::Advanced)
                )
                .on_press(Message::DismissModal)
                .padding([6, 16])
                .style(cancel_button_style),
                Space::new().width(Length::Fill),
                keyboard::button(
                    text(t(locale, "modal.confirm.confirm"))
                        .size(13)
                        .shaping(Shaping::Advanced)
                )
                .on_press(Message::ConfirmModal(on_confirm_clone))
                .padding([6, 16])
                .style(confirm_button_style),
            ];

            let content_col = column![
                title,
                Space::new().height(12.0),
                msg,
                Space::new().height(16.0),
                buttons,
            ]
            .spacing(4);

            container(content_col.padding(20))
                .width(Length::Fixed(380.0))
                .style(modal_box_style)
                .into()
        }

        ModalState::SearchIndexRepair => {
            let buttons = row![
                keyboard::button(text(t(locale, "searchIndexRepair.later")).size(13))
                    .on_press(Message::DismissModal)
                    .padding([6, 16])
                    .style(cancel_button_style),
                Space::new().width(Length::Fill),
                keyboard::button(text(t(locale, "searchIndexRepair.rebuildNow")).size(13))
                    .on_press(Message::ConfirmModal(ConfirmAction::RebuildSearchIndex))
                    .padding([6, 16])
                    .style(confirm_button_style),
            ];
            let content = column![
                text(t(locale, "searchIndexRepair.title"))
                    .size(16)
                    .shaping(Shaping::Advanced)
                    .color(Color::WHITE),
                Space::new().height(12.0),
                text(t(locale, "searchIndexRepair.message"))
                    .size(13)
                    .shaping(Shaping::Advanced)
                    .color(Color::from_rgb(0.80, 0.80, 0.85)),
                Space::new().height(16.0),
                buttons,
            ];
            container(content.padding(20))
                .width(Length::Fixed(420.0))
                .style(modal_box_style)
                .into()
        }
        ModalState::FindReplace {
            query,
            replacement,
            show_replace,
        } => {
            let find = text_input(&t(locale, "find.query"), &query)
                .on_input(Message::FindQueryChanged)
                .on_submit(Message::FindNext)
                .style(inputs::field_style);
            let mut fields = column![find].spacing(8);
            if show_replace {
                fields = fields.push(
                    text_input(&t(locale, "find.replacement"), &replacement)
                        .on_input(Message::ReplaceQueryChanged)
                        .style(inputs::field_style),
                );
            }
            let mut actions = row![
                keyboard::button(text(t(locale, "find.next")))
                    .on_press(Message::FindNext)
                    .style(inputs::primary_button),
            ]
            .spacing(8);
            if show_replace {
                actions = actions
                    .push(
                        keyboard::button(text(t(locale, "find.replace")))
                            .on_press(Message::ReplaceCurrent)
                            .style(inputs::secondary_button),
                    )
                    .push(
                        keyboard::button(text(t(locale, "find.replaceAll")))
                            .on_press(Message::ReplaceAll)
                            .style(inputs::secondary_button),
                    );
            }
            container(
                column![
                    text(if show_replace {
                        t(locale, "find.titleReplace")
                    } else {
                        t(locale, "find.titleFind")
                    })
                    .size(16),
                    fields,
                    actions,
                ]
                .spacing(12),
            )
            .width(Length::Fixed(440.0))
            .padding(20)
            .style(modal_box_style)
            .into()
        }

        ModalState::SearchIndexRebuilding {
            task_id,
            cancellation_requested,
        } => {
            let mut content = column![
                text(t(locale, "searchIndexRepair.rebuildingTitle"))
                    .size(16)
                    .shaping(Shaping::Advanced)
                    .color(Color::WHITE),
                Space::new().height(12.0),
                text(t(locale, "searchIndexRepair.rebuildingMessage"))
                    .size(13)
                    .shaping(Shaping::Advanced)
                    .color(Color::from_rgb(0.80, 0.80, 0.85)),
            ];
            content = if cancellation_requested {
                content.push(Space::new().height(16.0)).push(
                    text(t(locale, "tasks.status.cancelling"))
                        .size(13)
                        .shaping(Shaping::Advanced)
                        .color(Color::from_rgb(0.80, 0.80, 0.85)),
                )
            } else {
                content.push(Space::new().height(16.0)).push(row![
                    Space::new().width(Length::Fill),
                    keyboard::button(text(t(locale, "tasks.cancelTask")).size(13))
                        .on_press(Message::CancelTask(
                            crate::state::navigation::TaskSource::Local,
                            task_id,
                        ))
                        .padding([6, 16])
                        .style(cancel_button_style),
                ])
            };
            container(content.padding(20))
                .width(Length::Fixed(420.0))
                .style(modal_box_style)
                .into()
        }

        ModalState::PostInsertLink {
            post_id: _post_id,
            title: _title,
            insertion_point: _,
            results,
            search_query,
            active_tab,
            external_url,
            external_text,
        } => {
            let internal_tab = keyboard::button(
                text(t(locale, "modal.postInsertLink.tabInternal"))
                    .size(13)
                    .shaping(Shaping::Advanced)
                    .color(if active_tab == PostInsertLinkTab::Internal {
                        Color::WHITE
                    } else {
                        Color::from_rgb(0.60, 0.60, 0.65)
                    }),
            )
            .on_press(Message::PostEditor(PostEditorMsg::PostInsertLinkTabSwitch(
                PostInsertLinkTab::Internal,
            )))
            .padding([8, 16])
            .style(cancel_button_style);

            let external_tab = keyboard::button(
                text(t(locale, "modal.postInsertLink.tabExternal"))
                    .size(13)
                    .shaping(Shaping::Advanced)
                    .color(if active_tab == PostInsertLinkTab::External {
                        Color::WHITE
                    } else {
                        Color::from_rgb(0.60, 0.60, 0.65)
                    }),
            )
            .on_press(Message::PostEditor(PostEditorMsg::PostInsertLinkTabSwitch(
                PostInsertLinkTab::External,
            )))
            .padding([8, 16])
            .style(cancel_button_style);

            let tabs = row![internal_tab, external_tab].spacing(4);

            let search_input = text_input(
                t(locale, "modal.postInsertLink.searchPlaceholder").as_str(),
                &search_query,
            )
            .size(13)
            .on_input(|s| Message::PostEditor(PostEditorMsg::PostInsertLinkSearch(s)))
            .padding([6, 10])
            .width(Length::Fill)
            .style(inputs::field_style);

            let internal_content: Element<'static, Message> = if results.is_empty() {
                column![
                    search_input,
                    Space::new().height(12.0),
                    text(t(locale, "modal.postInsertLink.emptyState"))
                        .size(12)
                        .shaping(Shaping::Advanced)
                        .color(Color::from_rgb(0.55, 0.55, 0.60)),
                ]
                .spacing(4)
                .into()
            } else {
                let mut column = column![search_input, Space::new().height(12.0)];
                for link in results {
                    column = column.push(
                        keyboard::button(
                            row![
                                column![
                                    text(link.title.clone())
                                        .size(13)
                                        .shaping(Shaping::Advanced)
                                        .color(Color::WHITE),
                                    text(link.canonical_url.clone())
                                        .size(11)
                                        .shaping(Shaping::Advanced)
                                        .color(Color::from_rgb(0.58, 0.58, 0.64)),
                                ]
                                .spacing(2)
                                .width(Length::Fill),
                                container(
                                    text(link.status.clone())
                                        .size(10)
                                        .shaping(Shaping::Advanced)
                                        .color(match link.status.as_str() {
                                            "published" => Color::from_rgb(0.52, 0.82, 0.60),
                                            "archived" => Color::from_rgb(0.70, 0.70, 0.74),
                                            _ => Color::from_rgb(0.90, 0.78, 0.35),
                                        }),
                                )
                                .padding([3, 8])
                                .style(|_: &Theme| {
                                    container::Style {
                                        background: Some(Background::Color(inputs::SURFACE_BG)),
                                        border: Border {
                                            color: inputs::BORDER_COLOR,
                                            width: 1.0,
                                            radius: 999.0.into(),
                                        },
                                        ..container::Style::default()
                                    }
                                }),
                            ]
                            .spacing(12)
                            .align_y(Alignment::Center),
                        )
                        .on_press(Message::PostEditor(PostEditorMsg::PostInsertLinkSelected(
                            link.post_id.clone(),
                        )))
                        .padding([8, 12])
                        .style(|_: &Theme, _| button::Style {
                            background: Some(Background::Color(inputs::HOVER_BG)),
                            text_color: Color::from_rgb(0.85, 0.85, 0.90),
                            border: Border {
                                color: inputs::BORDER_COLOR,
                                width: 1.0,
                                radius: 6.0.into(),
                            },
                            ..button::Style::default()
                        }),
                    );
                }
                column.spacing(4).into()
            };

            let external_content: Element<'static, Message> = column![
                text_input(
                    t(locale, "modal.postInsertLink.externalUrlPlaceholder").as_str(),
                    &external_url,
                )
                .size(13)
                .on_input(|s| Message::PostEditor(PostEditorMsg::PostInsertLinkUrlChanged(s)))
                .padding([6, 10])
                .width(Length::Fill)
                .style(inputs::field_style),
                text_input(
                    t(locale, "modal.postInsertLink.externalTextPlaceholder").as_str(),
                    &external_text,
                )
                .size(13)
                .on_input(|s| Message::PostEditor(PostEditorMsg::PostInsertLinkTextChanged(s)))
                .padding([6, 10])
                .width(Length::Fill)
                .style(inputs::field_style),
                Space::new().height(12.0),
                row![
                    Space::new().width(Length::Fill),
                    keyboard::button(text(t(locale, "modal.postInsertLink.insert")))
                        .on_press(Message::PostEditor(
                            PostEditorMsg::PostInsertLinkExternalInsert
                        ))
                        .padding([6, 16])
                        .style(confirm_button_style),
                ]
            ]
            .spacing(8)
            .into();

            let create_post_btn: Element<'static, Message> = keyboard::button(
                text(t(locale, "modal.postInsertLink.createPost"))
                    .size(12)
                    .shaping(Shaping::Advanced)
                    .color(Color::from_rgb(0.60, 0.60, 0.65)),
            )
            .on_press(Message::PostEditor(PostEditorMsg::PostInsertLinkCreate))
            .padding([6, 12])
            .style(|_: &Theme, _| button::Style {
                background: Some(Background::Color(inputs::SURFACE_BG)),
                text_color: Color::from_rgb(0.60, 0.60, 0.65),
                border: Border {
                    color: inputs::BORDER_COLOR,
                    width: 1.0,
                    radius: 6.0.into(),
                },
                ..button::Style::default()
            })
            .into();

            let cancel_text = text(t(locale, "modal.postInsertLink.cancel"))
                .size(13)
                .shaping(Shaping::Advanced);

            let trailing_button: Element<'static, Message> =
                if active_tab == PostInsertLinkTab::Internal {
                    create_post_btn
                } else {
                    Space::new().width(0.0).into()
                };

            let buttons = row![
                keyboard::button(cancel_text)
                    .on_press(Message::DismissModal)
                    .padding([6, 16])
                    .style(cancel_button_style),
                Space::new().width(Length::Fill),
                trailing_button,
            ]
            .spacing(8);

            let content = column![
                tabs,
                Space::new().height(12.0),
                if active_tab == PostInsertLinkTab::Internal {
                    internal_content
                } else {
                    external_content
                },
                Space::new().height(16.0),
                buttons,
            ]
            .spacing(4);

            container(content.padding(20))
                .width(Length::Fixed(480.0))
                .style(modal_box_style)
                .into()
        }

        ModalState::InsertMedia {
            post_id: _post_id,
            title: _title,
            insertion_point: _,
            media_list,
            search_query,
            link_only: _,
        } => {
            let title = text(t(locale, "modal.insertMedia.title"))
                .size(16)
                .shaping(Shaping::Advanced)
                .color(Color::WHITE);

            let search_input = text_input(
                t(locale, "modal.insertMedia.searchPlaceholder").as_str(),
                &search_query,
            )
            .size(13)
            .on_input(|s| Message::PostEditor(PostEditorMsg::PostInsertMediaSearch(s)))
            .padding([6, 10])
            .width(Length::Fill)
            .style(inputs::field_style);

            let media_items: Vec<Element<'static, Message>> = media_list
                .iter()
                .map(|m| {
                    let is_image = m.mime_type.starts_with("image/");
                    let title = m.title.as_deref().unwrap_or("").to_string();
                    let original_name = m.original_name.clone();
                    let thumb: Element<'static, Message> =
                        if let Some(path) = resolve_thumbnail_file(data_dir, m) {
                            image(path)
                                .width(Length::Fixed(120.0))
                                .height(Length::Fixed(90.0))
                                .into()
                        } else if is_image {
                            text("\u{1F5BC}")
                                .size(32)
                                .shaping(Shaping::Advanced)
                                .color(Color::from_rgb(0.40, 0.40, 0.45))
                                .into()
                        } else {
                            text("\u{1F4C4}")
                                .size(32)
                                .shaping(Shaping::Advanced)
                                .color(Color::from_rgb(0.40, 0.40, 0.45))
                                .into()
                        };

                    let media_title = text(title.clone())
                        .size(12)
                        .shaping(Shaping::Advanced)
                        .color(Color::from_rgb(0.85, 0.85, 0.90));

                    let media_name = text(original_name)
                        .size(10)
                        .shaping(Shaping::Advanced)
                        .color(Color::from_rgb(0.55, 0.55, 0.60));

                    let media_col = column![
                        container(thumb)
                            .width(Length::Fill)
                            .height(Length::Fill)
                            .center_x(Length::Fill)
                            .center_y(Length::Fill)
                            .style(|_| container::Style {
                                background: Some(Background::Color(inputs::SURFACE_BG)),
                                border: Border {
                                    color: inputs::BORDER_COLOR,
                                    width: 1.0,
                                    radius: 6.0.into(),
                                },
                                ..container::Style::default()
                            }),
                        Space::new().height(8.0),
                        media_title,
                        media_name,
                    ]
                    .spacing(4)
                    .align_x(Alignment::Center);

                    let btn = keyboard::button(media_col)
                        .on_press(Message::PostEditor(PostEditorMsg::PostInsertMediaSelected(
                            m.id.clone(),
                        )))
                        .padding(8)
                        .style(|_: &Theme, _| button::Style {
                            background: Some(Background::Color(inputs::ELEVATED_BG)),
                            border: Border {
                                color: inputs::BORDER_COLOR,
                                width: 1.0,
                                radius: 8.0.into(),
                            },
                            ..button::Style::default()
                        });
                    btn.into()
                })
                .collect();

            let mut grid = column![].spacing(12).width(Length::Fill);
            let mut media_items = media_items.into_iter();
            loop {
                let mut grid_row = row![].spacing(12).width(Length::Fill);
                let mut pushed_any = false;
                for item in media_items.by_ref().take(3) {
                    grid_row = grid_row.push(item);
                    pushed_any = true;
                }
                if !pushed_any {
                    break;
                }
                grid = grid.push(grid_row);
            }

            let media_list_col: Element<'static, Message> = if media_list.is_empty() {
                column![
                    search_input,
                    Space::new().height(12.0),
                    text(t(locale, "modal.postInsertLink.emptyState"))
                        .size(12)
                        .shaping(Shaping::Advanced)
                        .color(Color::from_rgb(0.55, 0.55, 0.60)),
                ]
                .spacing(4)
                .into()
            } else {
                column![
                    search_input,
                    Space::new().height(16.0),
                    container(grid).width(Length::Fill).center_x(Length::Fill),
                ]
                .spacing(8)
                .width(Length::Fill)
                .into()
            };

            let cancel_text = text(t(locale, "modal.insertMedia.cancel"))
                .size(13)
                .shaping(Shaping::Advanced);

            let buttons = row![
                keyboard::button(cancel_text)
                    .on_press(Message::DismissModal)
                    .padding([6, 16])
                    .style(cancel_button_style),
            ];

            let content = column![
                title,
                Space::new().height(12.0),
                media_list_col,
                Space::new().height(16.0),
                buttons,
            ]
            .spacing(4)
            .width(Length::Fill);

            container(content.padding(20))
                .width(Length::Fixed(580.0))
                .height(Length::Fixed(480.0))
                .style(modal_box_style)
                .into()
        }

        ModalState::PostGallery {
            post_id: _post_id,
            title: _title,
            media_list,
            selected_index,
        } => {
            let image_media: Vec<Media> = media_list
                .iter()
                .filter(|m| m.mime_type.starts_with("image/"))
                .cloned()
                .collect();

            let media_items: Vec<Element<'static, Message>> = image_media
                .iter()
                .enumerate()
                .map(|(index, m)| {
                    let title = m.title.as_deref().unwrap_or("").to_string();
                    let thumb: Element<'static, Message> =
                        if let Some(path) = resolve_thumbnail_file(data_dir, m) {
                            image(path)
                                .width(Length::Fixed(180.0))
                                .height(Length::Fixed(120.0))
                                .into()
                        } else {
                            text("\u{1F5BC}")
                                .size(32)
                                .shaping(Shaping::Advanced)
                                .color(Color::from_rgb(0.40, 0.40, 0.45))
                                .into()
                        };

                    let media_title = text(title)
                        .size(12)
                        .shaping(Shaping::Advanced)
                        .color(Color::from_rgb(0.85, 0.85, 0.90));

                    let media_col = column![
                        container(thumb)
                            .width(Length::Fill)
                            .height(Length::Fill)
                            .center_x(Length::Fill)
                            .center_y(Length::Fill)
                            .style(|_| container::Style {
                                background: Some(Background::Color(inputs::SURFACE_BG)),
                                border: Border {
                                    color: inputs::BORDER_COLOR,
                                    width: 1.0,
                                    radius: 6.0.into(),
                                },
                                ..container::Style::default()
                            }),
                        Space::new().height(8.0),
                        media_title,
                    ]
                    .spacing(4)
                    .align_x(Alignment::Center);

                    let btn = keyboard::button(media_col)
                        .on_press(Message::PostEditor(
                            PostEditorMsg::PostGalleryImageSelected(index),
                        ))
                        .padding(8)
                        .style(|_: &Theme, _| button::Style {
                            background: Some(Background::Color(inputs::ELEVATED_BG)),
                            border: Border {
                                color: inputs::BORDER_COLOR,
                                width: 1.0,
                                radius: 8.0.into(),
                            },
                            ..button::Style::default()
                        });
                    btn.into()
                })
                .collect();

            let mut grid = column![].spacing(12).width(Length::Fill);
            let mut media_items = media_items.into_iter();
            loop {
                let mut grid_row = row![].spacing(12).width(Length::Fill);
                let mut pushed_any = false;
                for item in media_items.by_ref().take(3) {
                    grid_row = grid_row.push(item);
                    pushed_any = true;
                }
                if !pushed_any {
                    break;
                }
                grid = grid.push(grid_row);
            }

            let close_text = text(t(locale, "modal.postGallery.close"))
                .size(13)
                .shaping(Shaping::Advanced);

            let close_button = keyboard::button(close_text)
                .on_press(Message::DismissModal)
                .padding([6, 16])
                .style(cancel_button_style);

            let lightbox: Element<'static, Message> = if let Some(index) = selected_index {
                if let Some(selected) = image_media.get(index) {
                    let preview: Element<'static, Message> =
                        if let Some(path) = resolve_media_file(data_dir, selected) {
                            image(path)
                                .width(Length::Fill)
                                .height(Length::Fixed(420.0))
                                .into()
                        } else {
                            text(t(locale, "modal.postGallery.unavailable"))
                                .size(13)
                                .shaping(Shaping::Advanced)
                                .into()
                        };
                    column![
                        container(preview)
                            .width(Length::Fill)
                            .center_x(Length::Fill),
                        row![
                            keyboard::button(text("<"))
                                .on_press(Message::PostEditor(PostEditorMsg::PostGalleryPrevious))
                                .padding([6, 12])
                                .style(cancel_button_style),
                            Space::new().width(Length::Fill),
                            text(format!("{} / {}", index + 1, image_media.len()))
                                .size(12)
                                .shaping(Shaping::Advanced),
                            Space::new().width(Length::Fill),
                            keyboard::button(text(">"))
                                .on_press(Message::PostEditor(PostEditorMsg::PostGalleryNext))
                                .padding([6, 12])
                                .style(cancel_button_style),
                            Space::new().width(12.0),
                            keyboard::button(text(t(locale, "modal.postGallery.backToGrid")))
                                .on_press(Message::PostEditor(
                                    PostEditorMsg::PostGalleryCloseLightbox
                                ))
                                .padding([6, 12])
                                .style(cancel_button_style),
                        ]
                        .align_y(Alignment::Center)
                    ]
                    .spacing(12)
                    .into()
                } else {
                    Space::new().height(0.0).into()
                }
            } else {
                Space::new().height(0.0).into()
            };

            let content = column![
                if selected_index.is_some() {
                    lightbox
                } else {
                    container(grid).center_x(Length::Fill).into()
                },
                Space::new().height(16.0),
                close_button,
            ]
            .spacing(4);

            container(content.padding(20))
                .width(Length::Fill)
                .height(Length::Fill)
                .style(modal_box_style)
                .into()
        }

        ModalState::AISuggestions { target, fields } => {
            let title = text(t(locale, "modal.aiSuggestions.title"))
                .size(16)
                .shaping(Shaping::Advanced)
                .color(Color::WHITE);

            let rows = fields
                .iter()
                .enumerate()
                .map(|(index, field)| {
                    let toggle = keyboard::focusable(
                        checkbox(field.accepted)
                            .label(field.label.clone())
                            .on_toggle_maybe((!field.locked).then_some(move |value| {
                                Message::ToggleAiSuggestionField(index, value)
                            }))
                            .size(16)
                            .text_size(13),
                        !field.locked,
                    );
                    container(
                        column![
                            toggle,
                            row![
                                container(
                                    text(field.current_value.clone())
                                        .size(12)
                                        .color(Color::from_rgb(0.58, 0.58, 0.64))
                                )
                                .width(Length::FillPortion(1)),
                                text("→").size(14).color(Color::from_rgb(0.62, 0.66, 0.74)),
                                container(
                                    text(field.suggested_value.clone())
                                        .size(12)
                                        .color(Color::WHITE)
                                )
                                .width(Length::FillPortion(1)),
                            ]
                            .spacing(10)
                            .align_y(Alignment::Center),
                        ]
                        .spacing(8),
                    )
                    .padding(10)
                    .style(|_: &Theme| container::Style {
                        background: Some(Background::Color(inputs::SURFACE_BG)),
                        border: Border {
                            color: inputs::BORDER_COLOR,
                            width: 1.0,
                            radius: 6.0.into(),
                        },
                        ..container::Style::default()
                    })
                    .into()
                })
                .collect::<Vec<Element<'static, Message>>>();

            let buttons = row![
                keyboard::button(text(t(locale, "common.cancel")).size(13))
                    .on_press(Message::DismissModal)
                    .padding([6, 16])
                    .style(cancel_button_style),
                Space::new().width(Length::Fill),
                keyboard::button(text(t(locale, "modal.aiSuggestions.applySelected")).size(13))
                    .on_press(Message::ApplyAiSuggestions(target, fields))
                    .padding([6, 16])
                    .style(confirm_button_style),
            ];

            let content = column![
                title,
                Space::new().height(12.0),
                scrollable(column(rows).spacing(8))
                    .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar()))
                    .style(inputs::scrollable_style)
                    .height(Length::Fixed(320.0)),
                Space::new().height(16.0),
                buttons,
            ]
            .spacing(4);

            container(content.padding(20))
                .width(Length::Fixed(640.0))
                .style(modal_box_style)
                .into()
        }

        ModalState::LanguagePicker {
            target,
            source_language,
            available_targets,
        } => {
            let title = text(t(locale, "modal.languagePicker.title"))
                .size(16)
                .shaping(Shaping::Advanced)
                .color(Color::WHITE);
            let subtitle = text(format!(
                "{}: {}",
                t(locale, "editor.language"),
                source_language
            ))
            .size(12)
            .shaping(Shaping::Advanced)
            .color(Color::from_rgb(0.60, 0.60, 0.68));

            let rows = if available_targets.is_empty() {
                vec![text(t(locale, "common.noResults")).size(12).into()]
            } else {
                available_targets
                    .into_iter()
                    .map(|language| {
                        let message = match &target {
                            AiEntityTarget::Post(_) => Message::PostEditor(
                                PostEditorMsg::TranslateTo(language.code.clone()),
                            ),
                            AiEntityTarget::Media(_) => Message::MediaEditor(
                                crate::views::media_editor::MediaEditorMsg::TranslateTo(
                                    language.code.clone(),
                                ),
                            ),
                        };
                        let status = language.existing_status.clone().unwrap_or_default();
                        keyboard::button(
                            row![
                                text(format!("{} {}", language.flag_emoji, language.name))
                                    .size(13)
                                    .shaping(Shaping::Advanced)
                                    .color(Color::WHITE),
                                Space::new().width(Length::Fill),
                                text(status)
                                    .size(11)
                                    .color(Color::from_rgb(0.60, 0.60, 0.68)),
                            ]
                            .align_y(Alignment::Center),
                        )
                        .on_press(message)
                        .padding([8, 12])
                        .style(cancel_button_style)
                        .into()
                    })
                    .collect::<Vec<Element<'static, Message>>>()
            };

            let content = column![
                title,
                subtitle,
                Space::new().height(12.0),
                column(rows).spacing(6),
                Space::new().height(16.0),
                keyboard::button(text(t(locale, "common.cancel")).size(13))
                    .on_press(Message::DismissModal)
                    .padding([6, 16])
                    .style(cancel_button_style),
            ]
            .spacing(4);

            container(content.padding(20))
                .width(Length::Fixed(420.0))
                .style(modal_box_style)
                .into()
        }
    };

    // Center the modal in a full-screen backdrop
    container(
        container(modal_content)
            .center_x(Length::Fill)
            .center_y(Length::Fill),
    )
    .width(Length::Fill)
    .height(Length::Fill)
    .style(backdrop_style)
    .into()
}

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

    #[test]
    fn external_link_markdown_requires_url() {
        assert_eq!(external_link_markdown("", "text"), None);
        assert_eq!(
            external_link_markdown("https://example.com", ""),
            Some("https://example.com".to_string())
        );
        assert_eq!(
            external_link_markdown("https://example.com", "Example"),
            Some("[Example](https://example.com)".to_string())
        );
    }

    #[test]
    fn gallery_step_wraps_in_both_directions() {
        assert_eq!(gallery_step(0, 3, -1), 2);
        assert_eq!(gallery_step(2, 3, 1), 0);
        assert_eq!(gallery_step(1, 3, 1), 2);
    }
}
