use super::*;
use iced::widget::column;

impl App {
    pub(super) fn chat_detail(&self) -> Element<'_, Message> {
        let Some(item) = self.selected_project() else {
            let open_project_content = row![icon(ICON_FOLDER_PLUS, 17), text("Open project…"),]
                .spacing(8)
                .align_y(Alignment::Center);
            let open_project = if self.database.is_some() && !self.choosing_folder {
                action_button(open_project_content).on_press(Message::ChooseProjectFolder)
            } else {
                action_button(open_project_content)
            };
            return container(
                column![
                    icon(ICON_SPARK, 36),
                    text("Start a local coding session").size(28),
                    text("Choose a project folder to create your first session.").size(14),
                    Space::new().height(10),
                    open_project,
                ]
                .spacing(10)
                .align_x(Alignment::Center),
            )
            .center_x(Length::Fill)
            .center_y(Length::Fill)
            .into();
        };

        let project = &item.project;
        let active_title = self.active_session_title(item);
        let selected_title = active_title.unwrap_or(project.name.as_str());
        let mut header = row![icon(ICON_FOLDER, 19), text(selected_title).size(18)];
        if let Some(session) = self.selected_session(item) {
            header = header.push(
                button(icon(ICON_MORE, 18))
                    .on_press(Message::OpenSessionMenu(session.id))
                    .style(button::text),
            );
        }
        let header = header
            .push(Space::new().width(Length::Fill))
            .spacing(10)
            .align_y(Alignment::Center);

        let body: Element<'_, Message> = if let Some(title) = active_title {
            let mut messages = column![].spacing(12);
            if self.conversation.is_empty() {
                messages = messages.push(
                    column![
                        text(title).size(26),
                        text("Run DeepSeek locally with the Rust Metal engine.").size(14),
                    ]
                    .spacing(8),
                );
            } else {
                let markdown_style = markdown::Style::from_palette(app_theme().palette());
                for (index, message) in self.conversation.iter().enumerate() {
                    if message.compaction {
                        messages = messages.push(
                            container(
                                column![
                                    text("Context compacted").size(11),
                                    text(&message.content).size(13).color(muted_text()),
                                ]
                                .spacing(5),
                            )
                            .padding(14)
                            .width(Length::Fill)
                            .style(preference_group_style),
                        );
                        continue;
                    }
                    if message.system {
                        continue;
                    }
                    if message.tool
                        && index > 0
                        && !crate::agent::stored_tool_cards(
                            self.config.model,
                            &self.conversation[index - 1].content,
                            None,
                            &self.conversation[index - 1].tool_approval_reasons,
                        )
                        .is_empty()
                    {
                        continue;
                    }
                    if message.user
                        && let Some(stats) = message.generation_stats
                    {
                        messages = messages.push(generation_summary(stats));
                    }
                    let label = if message.user {
                        "You"
                    } else if message.tool {
                        "Tool"
                    } else {
                        "DS4"
                    };
                    let active = self.generating && index + 1 == self.conversation.len();
                    let mut body = column![text(label).size(11)].spacing(5);
                    if let Some(reasoning) = &message.reasoning {
                        let reasoning_label =
                            match (message.reasoning_open, message.reasoning_complete, active) {
                                (true, false, true) => "▾ Thinking",
                                (false, false, true) => "› Thinking",
                                (true, false, false) => "▾ Reasoning (stopped)",
                                (false, false, false) => "› Reasoning (stopped)",
                                (true, true, _) => "▾ Reasoning",
                                (false, true, _) => "› Reasoning",
                            };
                        body = body.push(
                            button(text(reasoning_label).size(12))
                                .padding(0)
                                .style(button::text)
                                .on_press(Message::ToggleReasoning(index)),
                        );
                        if message.reasoning_open {
                            body = body.push(
                                text(if reasoning.is_empty() && active {
                                    "Thinking…"
                                } else {
                                    reasoning
                                })
                                .size(13)
                                .color(muted_text()),
                            );
                        }
                    }
                    if !message.content.is_empty() {
                        if message.user || message.tool || message.markdown.items().is_empty() {
                            body = body.push(
                                text_editor(&message.transcript)
                                    .id(iced::widget::Id::from(format!(
                                        "transcript-{}-{index}",
                                        message.id
                                    )))
                                    .on_action(move |action| {
                                        Message::TranscriptAction(index, action)
                                    })
                                    .padding(0)
                                    .size(14)
                                    .font(if message.tool {
                                        iced::Font::MONOSPACE
                                    } else {
                                        iced::Font::DEFAULT
                                    })
                                    .style(selectable_text_style),
                            );
                        } else {
                            body = body.push(
                                markdown::view(
                                    message.markdown.items(),
                                    markdown::Settings::with_text_size(14, markdown_style),
                                )
                                .map(Message::OpenLink),
                            );
                        }
                    } else if active && message.reasoning.is_none() {
                        body = body.push(
                            text(self.activity.as_deref().unwrap_or("Loading model…")).size(14),
                        );
                    }
                    if !message.user {
                        let stored_result = self
                            .conversation
                            .get(index + 1)
                            .filter(|message| message.tool)
                            .map(|message| message.content.as_str());
                        let cards = if stored_result.is_none() && {
                            #[cfg(target_os = "macos")]
                            {
                                !self.tool_cards.is_empty()
                            }
                            #[cfg(not(target_os = "macos"))]
                            {
                                false
                            }
                        } {
                            #[cfg(target_os = "macos")]
                            {
                                self.tool_cards.clone()
                            }
                            #[cfg(not(target_os = "macos"))]
                            {
                                Vec::new()
                            }
                        } else {
                            crate::agent::stored_tool_cards(
                                self.config.model,
                                &message.content,
                                stored_result,
                                &message.tool_approval_reasons,
                            )
                        };
                        if !cards.is_empty() {
                            body = body.push(tool_cards(cards));
                        }
                    }
                    let message_body = container(body).padding(14).width(Length::Fill);
                    messages = messages.push(if message.user {
                        message_body.style(chat_message_style)
                    } else {
                        message_body
                    });
                }
                if let Some(activity) = &self.activity {
                    messages = messages.push(
                        container(text(activity).size(13).color(muted_text()))
                            .padding(12)
                            .width(Length::Fill)
                            .style(preference_group_style),
                    );
                }
            }
            // Tab walks every focusable widget of every window, so a composer
            // left behind an open dialog would take a turn in that dialog's
            // field order. Behind a modal it becomes a plain look-alike that
            // cannot be focused; the modal dims it either way.
            let composer = text_editor(&self.composer)
                .placeholder(if self.generating {
                    "Add guidance to the queue…"
                } else {
                    "Ask DS4Server anything…"
                })
                .height(Length::Shrink)
                .max_height(160)
                .padding(12)
                .size(14)
                .wrapping(iced::widget::text::Wrapping::Word)
                .style(selectable_text_style);
            let composer: Element<'_, Message> = if self.modal_open() {
                composer.into()
            } else {
                composer
                    .id(composer_id())
                    .on_action(Message::ComposerAction)
                    .key_binding(|event| {
                        composer_enter_binding(&event.key, event.modifiers)
                            .or_else(|| text_editor::Binding::from_key_press(event))
                    })
                    .into()
            };
            let action = if self.generating {
                action_button(text("■").size(13))
                    .padding(8)
                    .style(stop_button_style)
                    .on_press(Message::StopGeneration)
            } else if self.composer.text().trim().is_empty() {
                action_button(icon(ICON_SEND, 18)).padding(8)
            } else {
                action_button(icon(ICON_SEND, 18))
                    .padding(8)
                    .on_press(Message::SubmitPrompt)
            };
            let context_fraction = if self.context_limit == 0 {
                0.0
            } else {
                self.context_used.min(self.context_limit) as f32 / self.context_limit as f32
            };
            let mut composer_content = column![composer].spacing(6);
            for queued in &self.queued_inputs {
                let mut queued = queued.replace('\n', " ");
                if queued.chars().count() > 120 {
                    queued = queued.chars().take(119).collect::<String>() + "…";
                }
                composer_content = composer_content.push(
                    text(format!("Queued · {queued}"))
                        .size(12)
                        .color(muted_text()),
                );
            }
            let project_control: Element<'_, Message> = if self.selected_session.is_none() {
                let choices = self
                    .projects
                    .iter()
                    .map(|item| ProjectChoice {
                        id: item.project.id,
                        name: item.project.name.clone(),
                    })
                    .collect::<Vec<_>>();
                let selected = choices
                    .iter()
                    .find(|choice| choice.id == project.id)
                    .cloned();
                pick_list(choices, selected, |choice| {
                    Message::DraftProjectChanged(choice.id)
                })
                .text_size(12)
                .padding([2, 6])
                .into()
            } else {
                row![icon(ICON_FOLDER, 14), text(&project.name).size(12)]
                    .spacing(4)
                    .align_y(Alignment::Center)
                    .into()
            };
            let branch_control = self.git_states.get(&project.id).map(|state| {
                pick_list(
                    state.branches.clone(),
                    state.current.clone(),
                    Message::SwitchGitBranch,
                )
                .placeholder(&state.label)
                .text_size(12)
                .padding([2, 6])
            });
            let reasoning_mode = self.config.reasoning_mode(self.config.model);
            let reasoning_control: Element<'_, Message> = if self.active_chat_count() > 0 {
                text(reasoning_mode.to_string()).size(12).into()
            } else {
                pick_list(
                    self.config.supported_reasoning_modes(self.config.model),
                    Some(reasoning_mode),
                    Message::ReasoningModeChanged,
                )
                .text_size(12)
                .padding([2, 6])
                .into()
            };
            composer_content =
                composer_content.push(
                    row![
                        icon(ICON_PAPERCLIP, 19),
                        tooltip(
                            context_pie(context_fraction, 19),
                            container(
                                text(format!(
                                    "{} / {} tokens ({:.0}%)",
                                    self.context_used,
                                    self.context_limit,
                                    context_fraction * 100.0
                                ))
                                .size(12)
                            )
                            .padding(10)
                            .style(preference_group_style),
                            tooltip::Position::Top,
                        )
                        .gap(6),
                        text(self.tokens_per_second.map_or_else(
                            || "— tok/s".to_owned(),
                            |speed| format!("{speed:.1} tok/s")
                        ))
                        .size(11)
                        .color(muted_text()),
                        Space::new().width(Length::Fill),
                        project_control,
                        branch_control,
                        pick_list(
                            &PERMISSION_MODES[..],
                            Some(self.permission_mode),
                            Message::PermissionModeChanged,
                        )
                        .text_size(12)
                        .padding([2, 6]),
                        reasoning_control,
                        icon(ICON_MODEL, 16),
                        text(self.config.model.to_string()).size(12),
                        action,
                    ]
                    .spacing(6)
                    .align_y(Alignment::Center),
                );
            if let Some(notice) = &self.context_notice {
                messages = messages.push(
                    container(
                        column![
                            text("Context rebuild").size(11),
                            text(notice).size(13).color(muted_text()),
                        ]
                        .spacing(5),
                    )
                    .padding(14)
                    .width(Length::Fill)
                    .style(preference_group_style),
                );
            }
            let transcript = if self.chat_follow_tail {
                scrollable(messages).anchor_bottom()
            } else {
                scrollable(messages)
            }
            .id(chat_scroll_id())
            .on_scroll(Message::ChatScrolled)
            .height(Length::Fill);
            let conversation = column![
                transcript,
                container(composer_content,)
                    .padding(16)
                    .width(Length::Fill)
                    .style(preference_group_style),
            ]
            .height(Length::Fill)
            .spacing(8);
            container(conversation)
                .max_width(860)
                .center_x(Length::Fill)
                .height(Length::Fill)
                .into()
        } else {
            container(
                column![
                    text(if item.sessions.is_empty() {
                        "No sessions yet"
                    } else {
                        "Choose a session"
                    })
                    .size(24),
                    text("Use the new session icon next to the project, or pick one from the sidebar.")
                        .size(14),
                ]
                .spacing(8)
                .align_x(Alignment::Center),
            )
            .center_x(Length::Fill)
            .center_y(Length::Fill)
            .into()
        };

        container(column![header, body].spacing(24))
            .width(Length::Fill)
            .height(Length::Fill)
            .padding(24)
            .into()
    }
}

fn composer_enter_binding(
    key: &iced::keyboard::Key,
    modifiers: iced::keyboard::Modifiers,
) -> Option<text_editor::Binding<Message>> {
    match key.as_ref() {
        iced::keyboard::Key::Named(iced::keyboard::key::Named::Enter) if modifiers.shift() => {
            Some(text_editor::Binding::Enter)
        }
        iced::keyboard::Key::Named(iced::keyboard::key::Named::Enter) => {
            Some(text_editor::Binding::Custom(Message::SubmitPrompt))
        }
        _ => None,
    }
}

fn selectable_text_style(
    theme: &Theme,
    _status: iced::widget::text_editor::Status,
) -> iced::widget::text_editor::Style {
    iced::widget::text_editor::Style {
        background: Background::Color(Color::TRANSPARENT),
        border: Border::default(),
        placeholder: muted_text(),
        value: theme.palette().text,
        selection: theme.extended_palette().primary.weak.color,
    }
}

fn generation_summary(stats: crate::app::generation::GenerationStats) -> Element<'static, Message> {
    column![
        row![
            Space::new().width(Length::Fill),
            text(format!(
                "{} · {} input · {} cached · {} output",
                format_duration(stats.duration_ms as f64 / 1_000.0),
                format_token_count(stats.input_tokens),
                format_token_count(stats.cached_tokens),
                format_token_count(stats.output_tokens),
            ))
            .size(11)
            .color(muted_text()),
        ],
        rule::horizontal(1),
    ]
    .spacing(5)
    .padding(Padding::ZERO.right(24))
    .into()
}

fn format_token_count(value: u32) -> String {
    let digits = value.to_string();
    let mut output = String::with_capacity(digits.len() + digits.len() / 3);
    for (index, digit) in digits.chars().enumerate() {
        if index > 0 && (digits.len() - index).is_multiple_of(3) {
            output.push(',');
        }
        output.push(digit);
    }
    output
}

fn tool_cards(cards: Vec<crate::agent::ToolCard>) -> Element<'static, Message> {
    let mut rows = column![].spacing(0);
    for (index, card) in cards.into_iter().enumerate() {
        if index > 0 {
            rows = rows.push(rule::horizontal(1));
        }
        let parameters = crate::agent::tool_parameters(&card.call);
        let call = crate::agent::tool_call_text(&card.call);
        let copy_call = tooltip(
            action_button(text("Copy call").size(11))
                .padding([5, 9])
                .on_press(Message::CopyToolText(call)),
            container(text("Copy tool name and all arguments").size(11))
                .padding(8)
                .style(preference_group_style),
            tooltip::Position::Top,
        )
        .gap(6);
        let copy_result = action_button(text("Copy result").size(11)).padding([5, 9]);
        let copy_result = if let Some(result) = &card.result {
            copy_result.on_press(Message::CopyToolText(result.clone()))
        } else {
            copy_result
        };
        let copy_result = tooltip(
            copy_result,
            container(text("Copy the complete tool result").size(11))
                .padding(8)
                .style(preference_group_style),
            tooltip::Position::Top,
        )
        .gap(6);
        let mut actions = row![copy_call, copy_result]
            .spacing(6)
            .align_y(Alignment::Center);
        if let Some(path) = card
            .result
            .as_deref()
            .and_then(crate::agent::tool_output_path)
        {
            actions = actions.push(
                tooltip(
                    action_button(text("Open output").size(11))
                        .padding([5, 9])
                        .on_press(Message::OpenToolOutput(path)),
                    container(text("Open the complete output file").size(11))
                        .padding(8)
                        .style(preference_group_style),
                    tooltip::Position::Top,
                )
                .gap(6),
            );
        }
        let mut name = row![text(card.call.name).size(13)]
            .spacing(5)
            .align_y(Alignment::Center);
        if let Some(reason) = &card.approval_reason {
            name = name.push(
                tooltip(
                    icon(ICON_ROBOT, 13),
                    container(text(reason.clone()).size(12))
                        .padding(8)
                        .max_width(320)
                        .style(preference_group_style),
                    tooltip::Position::Top,
                )
                .gap(5),
            );
        }
        let mut content = column![
            row![
                name,
                Space::new().width(Length::Fill),
                text(card.state.label()).size(11).color(muted_text()),
                actions,
            ]
            .spacing(8)
            .align_y(Alignment::Center),
            text(parameters).size(12).color(muted_text()),
        ]
        .spacing(5);
        if let Some(result) = card.result {
            let bounded = crate::agent::bounded_tool_text(&result, 1_200);
            content = content.push(text(bounded).font(iced::Font::MONOSPACE).size(12));
        }
        rows = rows.push(container(content).padding(10).width(Length::Fill));
    }
    container(rows)
        .width(Length::Fill)
        .style(preference_group_style)
        .into()
}

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

    #[test]
    fn chat_divider_groups_token_counts_by_thousands() {
        assert_eq!(format_token_count(0), "0");
        assert_eq!(format_token_count(999), "999");
        assert_eq!(format_token_count(1_000), "1,000");
        assert_eq!(format_token_count(12_345_678), "12,345,678");
    }

    #[test]
    fn composer_uses_shift_enter_for_line_breaks() {
        let enter = iced::keyboard::Key::Named(iced::keyboard::key::Named::Enter);
        assert!(matches!(
            composer_enter_binding(&enter, iced::keyboard::Modifiers::SHIFT),
            Some(text_editor::Binding::Enter)
        ));
        assert!(matches!(
            composer_enter_binding(&enter, iced::keyboard::Modifiers::empty()),
            Some(text_editor::Binding::Custom(Message::SubmitPrompt))
        ));
    }
}
