use crate::{
    app::{App, DialogKind, FieldValue, Hit, Mode, WindowKind, WindowState},
    commands::Command,
};
use ratatui::{
    layout::Rect,
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
    Frame,
};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

pub fn dos(n: u8) -> Color {
    [
        Color::Black,
        Color::Blue,
        Color::Green,
        Color::Cyan,
        Color::Red,
        Color::Magenta,
        Color::Yellow,
        Color::Gray,
        Color::DarkGray,
        Color::LightBlue,
        Color::LightGreen,
        Color::LightCyan,
        Color::LightRed,
        Color::LightMagenta,
        Color::LightYellow,
        Color::White,
    ][n.min(15) as usize]
}
fn style(app: &App, element: usize) -> Style {
    let (fg, bg) = app.options.colors[element];
    Style::default().fg(dos(fg)).bg(dos(bg))
}
fn put(f: &mut Frame, area: Rect, text: impl Into<String>, style: Style) {
    f.render_widget(Paragraph::new(text.into()).style(style), area);
}
pub fn expanded(text: &str, tab: usize) -> String {
    let mut out = String::new();
    let mut column = 0;
    for c in text.chars() {
        if c == '\t' {
            let n = tab - column % tab;
            out.extend(std::iter::repeat_n(' ', n));
            column += n;
        } else {
            out.push(c);
            column += c.width().unwrap_or(0);
        }
    }
    out
}
pub const STATUS: [(&str, Command); 5] = [
    ("<Shift+F1=Help>", Command::UsingHelp),
    ("<F6=Window>", Command::NextWindow),
    ("<F2=Code>", Command::Code),
    ("<F5=Run>", Command::Continue),
    ("<F8=Step>", Command::Step),
];

impl App {
    pub fn render(&mut self, f: &mut Frame) {
        let area = f.area();
        self.size = (area.width, area.height);
        self.hits.clear();
        if area.width < 80 || area.height < 25 {
            f.render_widget(Clear, area);
            put(
                f,
                area,
                "Terminal Basic benötigt mindestens 80×25 Zellen.",
                Style::default(),
            );
            return;
        }
        if self.session.fullscreen && self.dialog.is_none() && self.menu.is_none() {
            f.render_widget(tb_ui::screen::ScreenWidget(self.session.screen()), area);
            return;
        }
        let fill = self.options.desktop.to_string().repeat(area.width as usize);
        for row in 0..area.height {
            put(
                f,
                Rect::new(0, row, area.width, 1),
                fill.clone(),
                style(self, 1),
            );
        }
        let mut windows = self.windows.clone();
        windows.sort_by_key(|w| w.id == self.active);
        for w in windows {
            let rect = self.rect(&w);
            if rect.width < 2 || rect.height < 2 {
                continue;
            }
            let active = w.id == self.active;
            let content_style = style(
                self,
                if matches!(w.kind, WindowKind::Code(_)) {
                    2
                } else {
                    5
                },
            );
            f.render_widget(
                Block::default()
                    .borders(Borders::ALL)
                    .style(content_style)
                    .border_style(style(self, 4)),
                rect,
            );
            self.hits.push((rect, Hit::Window(w.id)));
            let title_style = style(self, if active { 3 } else { 4 });
            put(
                f,
                Rect::new(rect.x, rect.y, rect.width, 1),
                format!("[≡] {}", self.title(&w)),
                title_style,
            );
            if active {
                self.hits.push((
                    Rect::new(rect.x, rect.y, 3, 1),
                    Hit::Command(Command::ControlMenu),
                ));
            }
            if rect.width > 10 {
                let x = rect.right() - 6;
                put(f, Rect::new(x, rect.y, 6, 1), "[_][↑]", title_style);
                if active {
                    self.hits
                        .push((Rect::new(x, rect.y, 3, 1), Hit::Command(Command::Minimize)));
                    self.hits.push((
                        Rect::new(x + 3, rect.y, 3, 1),
                        Hit::Command(Command::Maximize),
                    ));
                }
            }
            if w.state == WindowState::Minimized {
                continue;
            }
            let inner = Rect::new(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);
            match w.kind {
                WindowKind::Code(view) => {
                    let v = self.project.view(view).unwrap();
                    let doc = self.project.document(v.document()).unwrap();
                    if self.mode == Mode::Designer
                        && Some(v.document()) == self.designer.document
                        && matches!(doc.content(), tb_vm::project_io::Content::Form(_))
                    {
                        self.design_render(f, inner);
                    } else {
                        let expansion = self.editor.expansions.get(&view);
                        let code = expansion.map(String::as_str).unwrap_or(doc.code());
                        let lines: Vec<_> = code.split('\n').collect();
                        let count = lines.len();
                        let longest = lines
                            .iter()
                            .map(|s| expanded(s, self.options.tab_width).width())
                            .max()
                            .unwrap_or(0);
                        let selection = if expansion.is_none() {
                            v.selection()
                        } else {
                            None
                        };
                        let mut start = 0;
                        let text = lines
                            .iter()
                            .enumerate()
                            .filter_map(|(row, line)| {
                                let base = start;
                                start += line.len() + 1;
                                if row < v.scroll_line {
                                    return None;
                                }
                                let mut col = 0;
                                let mut spans = Vec::new();
                                for (i, c) in line.char_indices() {
                                    if c == '\r' {
                                        continue;
                                    }
                                    let width = if c == '\t' {
                                        self.options.tab_width - col % self.options.tab_width
                                    } else {
                                        c.width().unwrap_or(0)
                                    };
                                    let end = col + width;
                                    if end > v.scroll_column
                                        && col < v.scroll_column + inner.width as usize
                                    {
                                        let selected = selection
                                            .as_ref()
                                            .is_some_and(|r| r.contains(&(base + i)));
                                        let text = if c == '\t'
                                            || col < v.scroll_column
                                            || end > v.scroll_column + inner.width as usize
                                        {
                                            " ".repeat(
                                                end.min(v.scroll_column + inner.width as usize)
                                                    - col.max(v.scroll_column),
                                            )
                                        } else {
                                            c.to_string()
                                        };
                                        spans.push(Span::styled(
                                            text,
                                            if selected {
                                                content_style.add_modifier(Modifier::REVERSED)
                                            } else {
                                                content_style
                                            },
                                        ));
                                    } else if width == 0 && !spans.is_empty() {
                                        spans.push(Span::raw(c.to_string()));
                                    }
                                    col = end;
                                }
                                Some(Line::from(spans))
                            })
                            .collect::<Vec<_>>();
                        f.render_widget(Paragraph::new(text).style(content_style), inner);
                        if count > inner.height as usize {
                            f.render_stateful_widget(
                                Scrollbar::new(ScrollbarOrientation::VerticalRight)
                                    .style(style(self, 4)),
                                rect,
                                &mut ScrollbarState::new(count)
                                    .position(v.scroll_line)
                                    .viewport_content_length(inner.height as usize),
                            );
                        }
                        if longest > inner.width as usize {
                            f.render_stateful_widget(
                                Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
                                    .style(style(self, 4)),
                                rect,
                                &mut ScrollbarState::new(longest)
                                    .position(v.scroll_column)
                                    .viewport_content_length(inner.width as usize),
                            );
                        }
                        if active
                            && expansion.is_none()
                            && self.dialog.is_none()
                            && self.menu.is_none()
                        {
                            let before = &doc.code()[..v.cursor.min(doc.code().len())];
                            let row = before.bytes().filter(|c| *c == b'\n').count();
                            let col = expanded(
                                before.rsplit('\n').next().unwrap_or(""),
                                self.options.tab_width,
                            )
                            .width();
                            if row >= v.scroll_line
                                && row - v.scroll_line < inner.height as usize
                                && col >= v.scroll_column
                                && col - v.scroll_column < inner.width as usize
                            {
                                f.set_cursor_position((
                                    inner.x + (col - v.scroll_column) as u16,
                                    inner.y + (row - v.scroll_line) as u16,
                                ));
                            }
                        }
                    }
                }
                WindowKind::Project => {
                    let button_width = 9.min(inner.width / 2);
                    for (i, (text, c)) in [("Form", Command::Form), ("Code", Command::Code)]
                        .into_iter()
                        .enumerate()
                    {
                        let r = Rect::new(
                            inner.x + i as u16 * button_width,
                            inner.y,
                            button_width,
                            3.min(inner.height),
                        );
                        f.render_widget(
                            Paragraph::new(text)
                                .block(
                                    Block::bordered()
                                        .border_type(ratatui::widgets::BorderType::Double)
                                        .border_style(if active {
                                            Style::default().fg(Color::White)
                                        } else {
                                            style(self, 4)
                                        }),
                                )
                                .style(content_style),
                            r,
                        );
                        self.hits.push((r, Hit::ProjectView(c == Command::Form)));
                    }
                    let members = self.project.members();
                    let visible = inner.height.saturating_sub(3) as usize;
                    let start = self
                        .selected_member
                        .saturating_sub(visible.saturating_sub(1));
                    for (row, id) in members.iter().enumerate().skip(start).take(visible) {
                        let r =
                            Rect::new(inner.x, inner.y + 3 + (row - start) as u16, inner.width, 1);
                        let doc = self.project.document(*id).unwrap();
                        let text = format!(
                            "{}{}",
                            if self.project.startup() == Some(*id) {
                                "▶ "
                            } else {
                                "  "
                            },
                            doc.source_path()
                                .file_name()
                                .unwrap_or_default()
                                .to_string_lossy()
                        );
                        put(
                            f,
                            r,
                            text,
                            if row == self.selected_member {
                                Style::default().fg(Color::White).bg(Color::Black)
                            } else {
                                content_style
                            },
                        );
                        self.hits.push((r, Hit::ProjectMember(row)));
                    }
                }
                kind @ (WindowKind::Toolbox | WindowKind::Palette | WindowKind::MenuDesign) => {
                    self.design_tool_render(f, kind, inner)
                }
                WindowKind::Output => {
                    f.render_widget(tb_ui::screen::ScreenWidget(self.session.screen()), inner)
                }
                kind => put(
                    f,
                    inner,
                    match kind {
                        WindowKind::Help => "Help · Inhalte folgen in Change 07",
                        _ => "Debugger-Inhalte folgen in Change 06",
                    },
                    content_style,
                ),
            }
        }
        if self.mode == Mode::Environment {
            let row = area.height - 1;
            put(
                f,
                Rect::new(0, row, area.width, 1),
                " ".repeat(area.width as usize),
                style(self, 6),
            );
            let mut x = 0;
            for (label, c) in STATUS {
                let width = label.len() as u16;
                if x + width > area.width.saturating_sub(11) {
                    break;
                }
                put(f, Rect::new(x, row, width, 1), label, style(self, 6));
                self.hits
                    .push((Rect::new(x, row, width, 1), Hit::Command(c)));
                x += width + 1;
            }
            let pos = self
                .active_window()
                .and_then(|w| {
                    if let WindowKind::Code(v) = w.kind {
                        self.project.view(v).ok()
                    } else {
                        None
                    }
                })
                .map(|v| {
                    let code = self.project.document(v.document()).unwrap().code();
                    let before = &code[..v.cursor.min(code.len())];
                    format!(
                        "{:05}:{:03}",
                        before.bytes().filter(|c| *c == b'\n').count() + 1,
                        expanded(
                            before.rsplit('\n').next().unwrap_or(""),
                            self.options.tab_width
                        )
                        .width()
                            + 1
                    )
                })
                .unwrap_or_else(|| "00001:001".into());
            put(f, Rect::new(area.width - 9, row, 9, 1), pos, style(self, 6));
            put(
                f,
                Rect::new(0, row - 1, area.width, 1),
                self.message.clone(),
                style(self, 1),
            );
        }
        if self.mode == Mode::Designer && self.properties {
            let (left, geometry) = self.design_bar();
            let width = (geometry.width() as u16 + 1).min(area.width);
            put(
                f,
                Rect::new(0, 0, area.width - width, 1),
                left,
                style(self, 0),
            );
            put(
                f,
                Rect::new(area.width - width, 0, width, 1),
                geometry,
                style(self, 0),
            );
            self.hits.push((
                Rect::new(0, 0, area.width / 2, 1),
                Hit::DesignAction("property"),
            ));
            self.hits.push((
                Rect::new(area.width / 2, 0, area.width - area.width / 2, 1),
                Hit::DesignAction("value"),
            ));
        } else {
            put(
                f,
                Rect::new(0, 0, area.width, 1),
                " ".repeat(area.width as usize),
                style(self, 0),
            );
            let menus = self.menus();
            let mut x = 0;
            for (i, m) in menus.iter().enumerate() {
                if m.title == "Help" {
                    x = area.width - 6;
                }
                let r = Rect::new(x, 0, m.title.len() as u16 + 2, 1);
                let spans = vec![
                    Span::raw(" "),
                    Span::styled(
                        &m.title[..1],
                        style(self, 0).add_modifier(Modifier::UNDERLINED),
                    ),
                    Span::raw(format!("{} ", &m.title[1..])),
                ];
                f.render_widget(
                    Paragraph::new(Line::from(spans)).style(
                        if self.menu.is_some_and(|(n, _)| n == i) {
                            Style::default().fg(Color::White).bg(Color::Black)
                        } else {
                            style(self, 0)
                        },
                    ),
                    r,
                );
                self.hits.push((r, Hit::Menu(i)));
                x += r.width;
            }
        }
        if let Some((menu, selected)) = self.menu {
            let entries = self.menu_entries(menu);
            let width = (entries.iter().map(|(s, _)| s.width()).max().unwrap_or(12) as u16 + 4)
                .min(area.width);
            let menus = self.menus();
            let x = if self.control_menu {
                self.active_window().map(|w| self.rect(w).x).unwrap_or(0)
            } else if menus[menu].title == "Help" {
                area.width - width
            } else {
                menus
                    .iter()
                    .take(menu)
                    .map(|m| m.title.len() as u16 + 2)
                    .sum::<u16>()
                    .min(area.width - width)
            };
            let visible = (area.height - 4) as usize;
            let start = selected.saturating_sub(visible - 1);
            let rect = Rect::new(x, 1, width, (entries.len().min(visible) + 2) as u16);
            f.render_widget(Clear, rect);
            f.render_widget(Block::bordered().style(style(self, 0)), rect);
            for (i, (text, c)) in entries.iter().enumerate().skip(start).take(visible) {
                let disabled = c.and_then(|c| self.availability(c));
                let st = if disabled.is_some() {
                    Style::default().fg(Color::DarkGray).bg(Color::Gray)
                } else if i == selected {
                    Style::default().fg(Color::White).bg(Color::Black)
                } else {
                    style(self, 0)
                };
                let r = Rect::new(x + 1, 2 + (i - start) as u16, width - 2, 1);
                let label = if *c == Some(Command::SyntaxChecking) && self.options.syntax_checking {
                    format!("• {text}")
                } else {
                    text.clone()
                };
                let mut spans = Vec::new();
                let mut underline = false;
                for c in label.chars() {
                    if c == '&' {
                        underline = true;
                        continue;
                    }
                    spans.push(Span::styled(
                        c.to_string(),
                        if underline {
                            st.add_modifier(Modifier::UNDERLINED)
                        } else {
                            st
                        },
                    ));
                    underline = false;
                }
                f.render_widget(Paragraph::new(Line::from(spans)).style(st), r);
                self.hits.push((r, Hit::MenuItem(i)));
            }
            if let Some((_, Some(c))) = entries.get(selected) {
                if let Some(reason) = self.availability(*c) {
                    put(
                        f,
                        Rect::new(0, area.height - 2, area.width, 1),
                        reason,
                        style(self, 0),
                    );
                }
            }
        }
        if let Some(d) = &self.dialog {
            let width = 76.min(area.width - 2);
            let height = (d.fields.len() as u16 + 8).min(area.height - 2);
            let rect = Rect::new(
                (area.width - width) / 2,
                (area.height - height) / 2,
                width,
                height,
            );
            let st = style(self, 5);
            f.render_widget(Clear, rect);
            f.render_widget(Block::bordered().title(d.title.clone()).style(st), rect);
            let visible = height.saturating_sub(7) as usize;
            let start = d
                .focus
                .min(d.fields.len().saturating_sub(1))
                .saturating_sub(visible.saturating_sub(1));
            for (i, field) in d.fields.iter().enumerate().skip(start).take(visible) {
                let r = Rect::new(rect.x + 2, rect.y + 1 + (i - start) as u16, width - 4, 1);
                let value = match &field.value {
                    FieldValue::Choice { .. } => format!("◄ {} ►", field.string()),
                    FieldValue::Toggle(v) => {
                        if *v {
                            "[x]".into()
                        } else {
                            "[ ]".into()
                        }
                    }
                    _ => field.string(),
                };
                let label = format!("{}: {}", field.label, value);
                let selected = d.focus == i;
                let field_style = if selected {
                    Style::default().fg(Color::White).bg(Color::Black)
                } else {
                    st
                };
                let caret = if let FieldValue::Text(text) = &field.value {
                    field.label.width() + 2 + text[..field.cursor].width()
                } else {
                    0
                };
                let scroll = if selected {
                    caret.saturating_sub(r.width.saturating_sub(1) as usize)
                } else {
                    0
                };
                f.render_widget(
                    Paragraph::new(label)
                        .style(field_style)
                        .scroll((0, scroll.min(u16::MAX as usize) as u16)),
                    r,
                );
                if selected
                    && matches!(field.value, FieldValue::Text(_))
                    && !(matches!(d.kind, DialogKind::Export(_)) && i == 0)
                {
                    f.set_cursor_position((r.x + (caret - scroll) as u16, r.y));
                }
                self.hits.push((r, Hit::DialogField(i)));
            }
            let export = matches!(d.kind, DialogKind::Export(_));
            let submit = Rect::new(rect.x + 2, rect.bottom() - 3, 16, 1);
            let cancel = Rect::new(rect.x + 20, rect.bottom() - 3, 14, 1);
            put(
                f,
                submit,
                if export { "[Prüfen]" } else { "[OK / Enter]" },
                if d.focus == d.fields.len() {
                    Style::default().fg(Color::White).bg(Color::Black)
                } else {
                    st
                },
            );
            put(f, cancel, "[Abbrechen]", st);
            self.hits.push((submit, Hit::DialogSubmit));
            self.hits.push((cancel, Hit::DialogCancel));
            if export {
                put(
                    f,
                    Rect::new(rect.x + 36, rect.bottom() - 3, width - 38, 1),
                    "[Erzeugen: Phase 6]",
                    Style::default().fg(Color::DarkGray).bg(Color::Gray),
                );
            }
            let mut status = d.error.clone();
            if export {
                if let Some(request) = &d.request {
                    status = format!(
                        "{}: {} · {}",
                        d.export_status.text(),
                        request.path.display(),
                        d.error
                    );
                } else if status.is_empty() {
                    status = crate::export::UNAVAILABLE.into();
                }
            }
            f.render_widget(
                Paragraph::new(status)
                    .wrap(ratatui::widgets::Wrap { trim: false })
                    .style(st),
                Rect::new(rect.x + 2, rect.bottom() - 6, width - 4, 3),
            );
            put(
                f,
                Rect::new(rect.x + 2, rect.bottom() - 2, width - 4, 1),
                "Tab: Feld · ◄/►: Auswahl · F2: Dateiwahl · Esc: Abbrechen",
                st,
            );
        }
    }
}
