use super::*;
use crate::tui::composer::ComposerState;
use unicode_width::UnicodeWidthStr;

pub(super) const MODEL_REASONING_EFFORT_OPTIONS: [&str; 5] =
    ["inherit", "low", "medium", "high", "xhigh"];

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum OverlayState {
    None,
    Agents {
        selected: usize,
    },
    Events {
        selected_event_id: Option<String>,
        detail_scroll: u16,
    },
    Transcript {
        scroll: u16,
    },
    AgentState {
        scroll: u16,
    },
    Tasks {
        selected: usize,
        detail_scroll: u16,
    },
    ModelPicker {
        filter: String,
        selected: usize,
    },
    ModelEffortPicker {
        model: String,
        selected: usize,
        return_filter: String,
        return_selected: usize,
    },
    DebugPromptInput {
        composer: ComposerState,
    },
    DebugPromptView {
        title: String,
        dump: String,
        scroll: u16,
    },
    HelpView {
        scroll: u16,
    },
}

pub(super) fn draw_overlay(frame: &mut Frame<'_>, app: &TuiApp) {
    match &app.overlay {
        OverlayState::None => {}
        OverlayState::Agents { selected } => draw_agents_overlay(frame, app, *selected),
        OverlayState::Events {
            selected_event_id,
            detail_scroll,
        } => draw_events_overlay(frame, app, selected_event_id.as_deref(), *detail_scroll),
        OverlayState::Transcript { scroll } => draw_transcript_overlay(frame, app, *scroll),
        OverlayState::AgentState { scroll } => draw_agent_state_overlay(frame, app, *scroll),
        OverlayState::Tasks {
            selected,
            detail_scroll,
        } => draw_tasks_overlay(frame, app, *selected, *detail_scroll),
        OverlayState::ModelPicker { filter, selected } => {
            draw_model_picker_overlay(frame, app, filter, *selected)
        }
        OverlayState::ModelEffortPicker {
            model, selected, ..
        } => draw_model_effort_picker_overlay(frame, app, model, *selected),
        OverlayState::DebugPromptInput { composer } => draw_input_modal(
            frame,
            "Debug Prompt",
            "Generate the effective prompt for the selected agent.",
            composer,
            76,
            7,
        ),
        OverlayState::DebugPromptView {
            title,
            dump,
            scroll,
        } => draw_large_text_overlay(frame, title, dump, *scroll),
        OverlayState::HelpView { scroll } => draw_help_overlay(frame, *scroll),
    }
}

fn draw_agents_overlay(frame: &mut Frame<'_>, app: &TuiApp, selected: usize) {
    let popup = centered_rect(92, 80, frame.area());
    let layout = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(34), Constraint::Min(0)])
        .split(popup);
    frame.render_widget(Clear, popup);

    let items = if app.agents.is_empty() {
        vec![ListItem::new("No public agents")]
    } else {
        app.agents
            .iter()
            .map(|agent| {
                let marker = if agent.identity.is_default_agent {
                    "*"
                } else {
                    " "
                };
                let label = format!(
                    "{} {} [{}]",
                    marker,
                    agent.identity.agent_id,
                    render::trim(&format!("{:?}", agent.agent.status), 12)
                );
                ListItem::new(label)
            })
            .collect::<Vec<_>>()
    };
    let list = List::new(items)
        .block(Block::default().title("Agents").borders(Borders::ALL))
        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
        .highlight_symbol("> ");
    let mut state = ListState::default();
    let selected = (!app.agents.is_empty()).then(|| selected.min(app.agents.len() - 1));
    state.select(selected);
    frame.render_stateful_widget(list, layout[0], &mut state);

    let text = app
        .agents
        .get(selected.unwrap_or(0))
        .map(render::render_summary)
        .unwrap_or_else(|| "No agent selected.".to_string());
    let detail = Paragraph::new(text)
        .block(Block::default().title("Agent Detail").borders(Borders::ALL))
        .wrap(Wrap { trim: false });
    frame.render_widget(detail, layout[1]);
}

fn draw_events_overlay(
    frame: &mut Frame<'_>,
    app: &TuiApp,
    selected_event_id: Option<&str>,
    detail_scroll: u16,
) {
    let popup = centered_rect(94, 82, frame.area());
    let layout = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(44), Constraint::Min(0)])
        .split(popup);
    frame.render_widget(Clear, popup);

    let events = app
        .projection
        .as_ref()
        .map(|projection| projection.event_log())
        .unwrap_or(&[]);

    let items = if events.is_empty() {
        vec![ListItem::new("No raw events observed yet")]
    } else {
        events
            .iter()
            .rev()
            .map(render::render_projection_event_summary)
            .map(ListItem::new)
            .collect::<Vec<_>>()
    };
    let mut state = ListState::default();
    if !events.is_empty() {
        state.select(Some(
            selected_event_id
                .and_then(|event_id| event_reverse_index(events, event_id))
                .unwrap_or(0)
                .min(events.len().saturating_sub(1)),
        ));
    }
    let list = List::new(items)
        .block(Block::default().title("Raw Events").borders(Borders::ALL))
        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
        .highlight_symbol("> ");
    frame.render_stateful_widget(list, layout[0], &mut state);

    let detail_text = events
        .iter()
        .rev()
        .nth(
            selected_event_id
                .and_then(|event_id| event_reverse_index(events, event_id))
                .unwrap_or(0),
        )
        .map(|event| {
            let payload = serde_json::to_string_pretty(&event.payload).unwrap_or_else(|_| {
                serde_json::to_string(&event.payload).unwrap_or_else(|_| "<invalid json>".into())
            });
            format!(
                "Id: {}\nSeq: {}\nTime: {}\nLane: {:?}\nType: {}\nSummary: {}\n\nPayload:\n{}",
                event.id,
                event.event_seq,
                event.ts.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S"),
                event.lane,
                event.kind,
                event.summary,
                payload
            )
        })
        .unwrap_or_else(|| "No event selected.".to_string());
    let detail = Paragraph::new(detail_text)
        .block(Block::default().title("Event Detail").borders(Borders::ALL))
        .scroll((detail_scroll, 0))
        .wrap(Wrap { trim: false });
    frame.render_widget(detail, layout[1]);
}

fn event_reverse_index(
    events: &[crate::tui::projection::ProjectionEventRecord],
    event_id: &str,
) -> Option<usize> {
    events.iter().rev().position(|event| event.id == event_id)
}

fn draw_transcript_overlay(frame: &mut Frame<'_>, app: &TuiApp, scroll: u16) {
    let popup = centered_rect(92, 82, frame.area());
    frame.render_widget(Clear, popup);
    let lines = conversation_events_overlay_lines(app);
    let body = if lines.is_empty() {
        "No conversation events observed yet.".to_string()
    } else {
        lines.join("\n\n")
    };
    let widget = Paragraph::new(body)
        .block(
            Block::default()
                .title("Conversation Events (Esc closes)")
                .borders(Borders::ALL),
        )
        .scroll((scroll, 0))
        .wrap(Wrap { trim: false });
    frame.render_widget(widget, popup);
}

pub(super) fn conversation_events_overlay_lines(app: &TuiApp) -> Vec<String> {
    let Some(projection) = app.projection.as_ref() else {
        return Vec::new();
    };
    let mut lines = projection
        .durable_conversation_events()
        .map(render::render_projection_event_summary)
        .collect::<Vec<_>>();
    lines.reverse();
    lines
}

fn draw_agent_state_overlay(frame: &mut Frame<'_>, app: &TuiApp, scroll: u16) {
    let popup = centered_rect(92, 82, frame.area());
    frame.render_widget(Clear, popup);
    let widget = Paragraph::new(render::render_agent_state_text(app))
        .block(
            Block::default()
                .title("Agent State (Esc closes)")
                .borders(Borders::ALL),
        )
        .scroll((scroll, 0))
        .wrap(Wrap { trim: false });
    frame.render_widget(widget, popup);
}

fn draw_tasks_overlay(frame: &mut Frame<'_>, app: &TuiApp, selected: usize, detail_scroll: u16) {
    let popup = centered_rect(92, 80, frame.area());
    let layout = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(38), Constraint::Min(0)])
        .split(popup);
    frame.render_widget(Clear, popup);

    let items = if app.tasks.is_empty() {
        vec![ListItem::new("No active tasks")]
    } else {
        app.tasks
            .iter()
            .rev()
            .map(render::render_task)
            .map(ListItem::new)
            .collect::<Vec<_>>()
    };
    let mut state = ListState::default();
    if !app.tasks.is_empty() {
        state.select(Some(selected.min(app.tasks.len().saturating_sub(1))));
    }
    let list = List::new(items)
        .block(Block::default().title("Tasks").borders(Borders::ALL))
        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
        .highlight_symbol("> ");
    frame.render_stateful_widget(list, layout[0], &mut state);

    let detail_text = app
        .tasks
        .iter()
        .rev()
        .nth(selected)
        .map(render::render_task_detail)
        .unwrap_or_else(|| "No active task selected.".to_string());
    let detail = Paragraph::new(detail_text)
        .block(Block::default().title("Task Detail").borders(Borders::ALL))
        .scroll((detail_scroll, 0))
        .wrap(Wrap { trim: false });
    frame.render_widget(detail, layout[1]);
}

fn draw_model_picker_overlay(frame: &mut Frame<'_>, app: &TuiApp, filter: &str, selected: usize) {
    let popup = centered_rect(92, 80, frame.area());
    let layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3),
            Constraint::Min(0),
            Constraint::Length(4),
        ])
        .split(popup);
    frame.render_widget(Clear, popup);

    let filter_text = if filter.is_empty() {
        "Filter: ".to_string()
    } else {
        format!("Filter: {filter}")
    };
    let filter_widget =
        Paragraph::new(filter_text).block(Block::default().title("Model").borders(Borders::ALL));
    frame.render_widget(filter_widget, layout[0]);

    let rows = crate::tui::model_picker::model_picker_rows(
        app.selected_agent_summary(),
        &app.model_availability,
        filter,
    );
    let items = if rows.is_empty() {
        vec![ListItem::new(
            "No runtime-provided model availability matches the filter",
        )]
    } else {
        rows.iter()
            .map(|row| {
                let status = if row.available { " " } else { "!" };
                ListItem::new(format!("{status} {}\n  {}", row.title, row.detail))
            })
            .collect::<Vec<_>>()
    };
    let mut state = ListState::default();
    if !rows.is_empty() {
        state.select(Some(selected.min(rows.len().saturating_sub(1))));
    }
    let list = List::new(items)
        .block(Block::default().title("Models").borders(Borders::ALL))
        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
        .highlight_symbol("> ");
    frame.render_stateful_widget(list, layout[1], &mut state);

    let current = app
        .selected_agent_summary()
        .map(render::render_model_status)
        .unwrap_or_else(|| "model: <no agent selected>".into());
    let help = Paragraph::new(format!(
        "{current}\nType to filter, Backspace edits, Up/Down moves, Enter selects, Esc cancels"
    ))
    .block(Block::default().borders(Borders::ALL))
    .wrap(Wrap { trim: false });
    frame.render_widget(help, layout[2]);
}

fn draw_model_effort_picker_overlay(
    frame: &mut Frame<'_>,
    app: &TuiApp,
    model: &str,
    selected: usize,
) {
    let popup = centered_rect(70, 42, frame.area());
    let layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3),
            Constraint::Min(0),
            Constraint::Length(3),
        ])
        .split(popup);
    frame.render_widget(Clear, popup);

    let header = Paragraph::new(format!("Model: {model}")).block(
        Block::default()
            .title("Reasoning Effort")
            .borders(Borders::ALL),
    );
    frame.render_widget(header, layout[0]);

    let items = MODEL_REASONING_EFFORT_OPTIONS
        .iter()
        .map(|option| {
            let detail = if *option == "inherit" {
                "use provider/runtime default"
            } else {
                "force reasoning effort"
            };
            ListItem::new(format!("{option}\n  {detail}"))
        })
        .collect::<Vec<_>>();
    let mut state = ListState::default();
    state.select(Some(
        selected.min(MODEL_REASONING_EFFORT_OPTIONS.len().saturating_sub(1)),
    ));
    let list = List::new(items)
        .block(Block::default().title("Effort").borders(Borders::ALL))
        .highlight_style(Style::default().add_modifier(Modifier::REVERSED))
        .highlight_symbol("> ");
    frame.render_stateful_widget(list, layout[1], &mut state);

    let current = app
        .selected_agent_summary()
        .map(render::render_model_status)
        .unwrap_or_else(|| "model: <no agent selected>".into());
    let help = Paragraph::new(format!(
        "{current}\nUp/Down moves, Enter confirms, Esc goes back"
    ))
    .block(Block::default().borders(Borders::ALL))
    .wrap(Wrap { trim: false });
    frame.render_widget(help, layout[2]);
}

fn draw_large_text_overlay(frame: &mut Frame<'_>, title: &str, text: &str, scroll: u16) {
    let popup = centered_rect(90, 80, frame.area());
    frame.render_widget(Clear, popup);
    let widget = Paragraph::new(text.to_string())
        .block(Block::default().title(title).borders(Borders::ALL))
        .scroll((scroll, 0))
        .wrap(Wrap { trim: false });
    frame.render_widget(widget, popup);
}

fn draw_help_overlay(frame: &mut Frame<'_>, scroll: u16) {
    let popup = centered_rect(88, 80, frame.area());
    frame.render_widget(Clear, popup);
    let mut help_lines = [
        "Main View",
        "  Type directly into the composer",
        "  Enter sends the current draft",
        "  Shift+Enter inserts a new line",
        "  Prefix with / to run a local TUI command",
        "  Esc clears the current draft",
        "",
        "Input History (composer must be empty)",
        "  Up browse older input history",
        "  Down browse newer input history",
        "",
        "Editing Shortcuts (work when composer has content)",
        "  Ctrl+A move cursor to start",
        "  Ctrl+E move cursor to end",
        "  Ctrl+B move cursor left",
        "  Ctrl+F move cursor right",
        "  Ctrl+K delete to end of line",
        "  Ctrl+U delete to start of line",
        "  Ctrl+W delete previous word",
        "  Ctrl+H delete previous character (backspace)",
        "  Ctrl+D delete next character",
        "",
        "Overlays",
        "  Esc closes the current overlay",
        "",
        "Quick Help",
        "  ? open this help when the composer is empty",
        "",
        "Scrolling",
        "  PgUp/PgDn scroll the active text view",
        "  Home/End jump to top/bottom",
        "",
        "Exit",
        "  Ctrl+C quit",
    ]
    .into_iter()
    .map(str::to_string)
    .collect::<Vec<_>>();
    help_lines.extend(["".into()]);
    help_lines.extend(crate::tui::input::slash_help_lines());
    help_lines.extend(["".into(), "Default Keymap".into()]);
    help_lines.extend(
        crate::tui::keymap::DEFAULT_BINDING_HINTS
            .iter()
            .map(|hint| {
                format!(
                    "  {}: {} ({})",
                    hint.context.label(),
                    hint.action,
                    hint.keys
                )
            }),
    );
    let help = help_lines.join("\n");
    let widget = Paragraph::new(help)
        .block(Block::default().title("Help").borders(Borders::ALL))
        .scroll((scroll, 0))
        .wrap(Wrap { trim: false });
    frame.render_widget(widget, popup);
}

fn draw_input_modal(
    frame: &mut Frame<'_>,
    title: &str,
    help: &str,
    composer: &ComposerState,
    width_percent: u16,
    height_rows: u16,
) {
    let popup = centered_rect_rows(width_percent, height_rows, frame.area());
    frame.render_widget(Clear, popup);
    let text = format!("{help}\n\n{}", composer.as_str());
    let widget = Paragraph::new(text)
        .block(Block::default().title(title).borders(Borders::ALL))
        .wrap(Wrap { trim: false });
    frame.render_widget(widget, popup);

    let prefix = format!("{help}\n\n{}", &composer.as_str()[..composer.cursor()]);
    let (x, y) = modal_cursor_position(popup, &prefix);
    frame.set_cursor_position(ratatui::layout::Position { x, y });
}

fn modal_cursor_position(area: Rect, rendered_prefix: &str) -> (u16, u16) {
    let input_width = area.width.saturating_sub(2).max(1);
    let lines = rendered_prefix.split('\n').collect::<Vec<_>>();
    let wrapped_rows_before = lines
        .iter()
        .take(lines.len().saturating_sub(1))
        .map(|line| wrapped_rows(line, input_width))
        .sum::<u16>();
    let last_line = lines.last().copied().unwrap_or("");
    let last_line_width = display_width(last_line);
    let soft_wrap_row = if last_line_width == 0 {
        0
    } else {
        last_line_width.saturating_sub(1) / input_width
    };
    let column = if last_line_width == 0 {
        0
    } else {
        last_line_width - soft_wrap_row * input_width
    };
    let max_x = area.x + area.width.saturating_sub(2);
    let max_y = area.y + area.height.saturating_sub(2);
    (
        (area.x + 1 + column).min(max_x),
        (area.y + 1 + wrapped_rows_before + soft_wrap_row).min(max_y),
    )
}

fn wrapped_rows(line: &str, visible_line_width: u16) -> u16 {
    let line_width = display_width(line);
    let rows = (line_width + visible_line_width.saturating_sub(1)) / visible_line_width;
    rows.max(1)
}

fn display_width(text: &str) -> u16 {
    UnicodeWidthStr::width(text).min(u16::MAX as usize) as u16
}

pub(super) fn centered_rect(width_percent: u16, height_percent: u16, area: Rect) -> Rect {
    let width_percent = width_percent.min(100);
    let height_percent = height_percent.min(100);
    let vertical = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - height_percent) / 2),
            Constraint::Percentage(height_percent),
            Constraint::Percentage((100 - height_percent) / 2),
        ])
        .split(area);
    let horizontal = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - width_percent) / 2),
            Constraint::Percentage(width_percent),
            Constraint::Percentage((100 - width_percent) / 2),
        ])
        .split(vertical[1]);
    horizontal[1]
}

pub(super) fn centered_rect_rows(width_percent: u16, height_rows: u16, area: Rect) -> Rect {
    let width_percent = width_percent.min(100);
    let height_rows = height_rows.clamp(1, area.height.max(1));
    let top = area.height.saturating_sub(height_rows) / 2;
    let bottom = area.height.saturating_sub(height_rows).saturating_sub(top);
    let vertical = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(top),
            Constraint::Length(height_rows),
            Constraint::Length(bottom),
        ])
        .split(area);
    let horizontal = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - width_percent) / 2),
            Constraint::Percentage(width_percent),
            Constraint::Percentage((100 - width_percent) / 2),
        ])
        .split(vertical[1]);
    horizontal[1]
}
