//! Per screen drawing.
//!
//! Each screen is a free function `draw_*(app, frame, area)`. Key handling
//! lives in `mod.rs::handle_key` since input dispatch is small enough to
//! keep in one place.

use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{
    Block, BorderType, Borders, Gauge, List, ListItem, ListState, Paragraph, Wrap,
};

use super::app::{App, BatchJob, BatchProgress, ParseStatus, Screen, SynthField};
use super::sparsity::{Sparsity, legend_lines};
use super::theme::{T, bad, border, dim, good, highlight, title, warn};
use powerio_matrix::pipeline::MatrixKind;

pub fn draw(frame: &mut Frame, app: &App) {
    let layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(1),
            Constraint::Min(0),
            Constraint::Length(7),
            Constraint::Length(1),
        ])
        .split(frame.area());

    draw_header(frame, app, layout[0]);
    match app.screen {
        Screen::Browse => draw_browse(frame, app, layout[1]),
        Screen::Inspect => draw_inspect(frame, app, layout[1]),
        Screen::Batch => draw_batch(frame, app, layout[1]),
        Screen::Synth => draw_synth(frame, app, layout[1]),
        Screen::Help => draw_help(frame, app, layout[1]),
    }
    draw_log(frame, app, layout[2]);
    draw_footer(frame, app, layout[3]);
}

fn draw_header(frame: &mut Frame, app: &App, area: Rect) {
    let title_text = format!(" powerio · {} ", app.screen.label());
    let line = Line::from(vec![
        Span::styled(title_text, title()),
        Span::raw("  "),
        Span::styled(
            format!(
                "data={}  out={}",
                app.data_dir.display(),
                app.out_dir.display()
            ),
            dim(),
        ),
    ]);
    frame.render_widget(Paragraph::new(line), area);
}

fn draw_footer(frame: &mut Frame, app: &App, area: Rect) {
    let mut spans: Vec<Span> = match app.screen {
        Screen::Browse => vec![
            kc("↑↓"),
            Span::raw(" select  "),
            kc("Space"),
            Span::raw(" toggle  "),
            kc("Enter"),
            Span::raw(" inspect  "),
            kc("b"),
            Span::raw(" batch  "),
            kc("g"),
            Span::raw(" synth  "),
            kc("?"),
            Span::raw(" help  "),
            kc("q"),
            Span::raw(" quit"),
        ],
        Screen::Inspect => vec![
            kc("Tab"),
            Span::raw(" matrix  "),
            kc("e"),
            Span::raw(" export  "),
            kc("s"),
            Span::raw(" toggle scheme  "),
            kc("Esc"),
            Span::raw(" back  "),
            kc("q"),
            Span::raw(" quit"),
        ],
        Screen::Batch => vec![
            kc("e"),
            Span::raw(" run  "),
            kc("m"),
            Span::raw(" cycle matrices  "),
            kc("r"),
            Span::raw(" cycle rhs  "),
            kc("Esc"),
            Span::raw(" back  "),
            kc("q"),
            Span::raw(" quit"),
        ],
        Screen::Synth => vec![
            kc("↑↓"),
            Span::raw(" field  "),
            kc("←→"),
            Span::raw(" tweak  "),
            kc("g"),
            Span::raw(" generate  "),
            kc("e"),
            Span::raw(" export  "),
            kc("Esc"),
            Span::raw(" back  "),
            kc("q"),
            Span::raw(" quit"),
        ],
        Screen::Help => vec![kc("Esc"), Span::raw(" back  "), kc("q"), Span::raw(" quit")],
    };
    if let Some((msg, _)) = &app.status {
        spans.push(Span::raw("    "));
        spans.push(Span::styled(
            format!("» {msg}"),
            Style::default().fg(T.accent),
        ));
    }
    frame.render_widget(Paragraph::new(Line::from(spans)), area);
}

fn kc(label: &str) -> Span<'_> {
    Span::styled(
        format!("[{label}]"),
        Style::default()
            .fg(T.accent_alt)
            .add_modifier(Modifier::BOLD),
    )
}

fn draw_browse(frame: &mut Frame, app: &App, area: Rect) {
    let split = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Percentage(45), Constraint::Percentage(55)])
        .split(area);

    let items: Vec<ListItem> = app
        .cases
        .iter()
        .enumerate()
        .map(|(i, c)| {
            let prefix = if app.multi_selected.contains(&i) {
                "[x] "
            } else {
                "[ ] "
            };
            let parsed_marker = match &c.parsed {
                ParseStatus::Loaded {
                    n_buses,
                    n_branches,
                    ..
                } => {
                    format!("  · {n_buses} buses / {n_branches} branches")
                }
                ParseStatus::Failed(_) => "  · parse failed".to_string(),
                ParseStatus::NotLoaded => String::new(),
            };
            let line = Line::from(vec![
                Span::raw(prefix),
                Span::styled(c.display_name.clone(), Style::default().fg(T.fg)),
                Span::styled(parsed_marker, dim()),
            ]);
            ListItem::new(line)
        })
        .collect();

    let list = List::new(items)
        .block(
            Block::default()
                .title(" Cases ")
                .borders(Borders::ALL)
                .border_type(BorderType::Rounded)
                .border_style(border()),
        )
        .highlight_style(highlight());

    let mut state = ListState::default();
    state.select(if app.cases.is_empty() {
        None
    } else {
        Some(app.selected)
    });
    frame.render_stateful_widget(list, split[0], &mut state);

    draw_browse_detail(frame, app, split[1]);
}

fn draw_browse_detail(frame: &mut Frame, app: &App, area: Rect) {
    let block = Block::default()
        .title(" Detail ")
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border());
    let inner = block.inner(area);
    frame.render_widget(block, area);

    let lines: Vec<Line> = if let Some(entry) = app.cases.get(app.selected) {
        let mut v = vec![
            Line::from(vec![
                Span::styled("path:    ", dim()),
                Span::raw(entry.path.display().to_string()),
            ]),
            Line::from(vec![
                Span::styled("name:    ", dim()),
                Span::raw(entry.display_name.clone()),
            ]),
        ];
        match &entry.parsed {
            ParseStatus::Loaded {
                n_buses,
                n_branches,
                base_mva,
            } => {
                v.push(Line::from(vec![
                    Span::styled("buses:   ", dim()),
                    Span::raw(n_buses.to_string()),
                ]));
                v.push(Line::from(vec![
                    Span::styled("branches:", dim()),
                    Span::raw(format!(" {n_branches}")),
                ]));
                v.push(Line::from(vec![
                    Span::styled("baseMVA: ", dim()),
                    Span::raw(format!("{base_mva}")),
                ]));
                v.push(Line::raw(""));
                v.push(Line::from(Span::styled(
                    "press Enter to inspect, Space to add to batch",
                    dim(),
                )));
            }
            ParseStatus::Failed(msg) => {
                v.push(Line::from(Span::styled(
                    format!("parse failed: {msg}"),
                    bad(),
                )));
            }
            ParseStatus::NotLoaded => {
                v.push(Line::from(Span::styled(
                    "not yet parsed (press Enter to inspect)",
                    dim(),
                )));
            }
        }
        v
    } else {
        vec![Line::from(Span::styled(
            "No `.m` cases found in data directory.",
            warn(),
        ))]
    };

    frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), inner);
}

fn draw_inspect(frame: &mut Frame, app: &App, area: Rect) {
    let split = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Length(7), Constraint::Min(0)])
        .split(area);

    // Header: case info + matrix kind selector
    let kind_strip = MatrixKind::ALL
        .iter()
        .map(|k| {
            let active = app.inspect.as_ref().is_some_and(|s| s.kind == *k);
            let style = if active { highlight() } else { dim() };
            Span::styled(format!(" {} ", k.label()), style)
        })
        .collect::<Vec<_>>();

    let mut header_lines = Vec::new();
    if let Some(state) = &app.inspect {
        header_lines.push(Line::from(vec![
            Span::styled("case: ", dim()),
            Span::styled(state.case.name.clone(), title()),
            Span::raw("    "),
            Span::styled("buses: ", dim()),
            Span::raw(state.case.buses.len().to_string()),
            Span::raw("   "),
            Span::styled("branches: ", dim()),
            Span::raw(state.case.branches.len().to_string()),
            Span::raw("   "),
            Span::styled("baseMVA: ", dim()),
            Span::raw(format!("{:.0}", state.case.base_mva)),
            Span::raw("   "),
            Span::styled("scheme: ", dim()),
            Span::raw(format!("{:?}", app.scheme)),
        ]));
    }
    header_lines.push(Line::raw(""));
    header_lines.push(Line::from(kind_strip));

    let header = Paragraph::new(header_lines).block(
        Block::default()
            .borders(Borders::ALL)
            .border_type(BorderType::Rounded)
            .border_style(border())
            .title(" Inspect "),
    );
    frame.render_widget(header, split[0]);

    let body = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
        .split(split[1]);

    draw_stats_panel(frame, app, body[0]);
    draw_sparsity_panel(frame, app, body[1]);
}

fn draw_stats_panel(frame: &mut Frame, app: &App, area: Rect) {
    let block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border())
        .title(" Stats ");
    let inner = block.inner(area);
    frame.render_widget(block, area);

    let Some(cell) = app.current_matrix() else {
        return;
    };
    let s = &cell.stats;
    let sddm_span = if cell.sddm {
        Span::styled("yes", good())
    } else {
        Span::styled("no", bad())
    };
    let m_sign_span = if s.m_matrix_sign {
        Span::styled("yes", good())
    } else {
        Span::styled("no", warn())
    };
    let dd_span = if s.min_dd_margin >= -1e-12 {
        Span::styled(format!("{:+.3e}", s.min_dd_margin), good())
    } else {
        Span::styled(format!("{:+.3e}", s.min_dd_margin), warn())
    };
    let lines = vec![
        kv("n", s.n.to_string()),
        kv("nnz", s.nnz.to_string()),
        kv_styled("min diag", format!("{:+.3e}", s.min_diag), good()),
        kv("max diag", format!("{:+.3e}", s.max_diag)),
        kv_inline("DD margin", dd_span),
        kv_inline("M-matrix sign", m_sign_span),
        kv_inline("SDDM", sddm_span),
        kv("zero skips", s.skipped_zero_impedance.to_string()),
        kv("‖A‖_F", format!("{:.3e}", s.frobenius_norm)),
        Line::raw(""),
        Line::from(Span::styled(
            "Tab cycles matrices · e exports · s toggles scheme",
            dim(),
        )),
    ];
    frame.render_widget(Paragraph::new(lines), inner);
}

fn kv(k: &str, v: String) -> Line<'static> {
    Line::from(vec![Span::styled(format!("{k:<14}"), dim()), Span::raw(v)])
}

fn kv_styled(k: &str, v: String, st: Style) -> Line<'static> {
    Line::from(vec![
        Span::styled(format!("{k:<14}"), dim()),
        Span::styled(v, st),
    ])
}

fn kv_inline<'a>(k: &str, span: Span<'a>) -> Line<'a> {
    Line::from(vec![Span::styled(format!("{k:<14}"), dim()), span])
}

fn draw_sparsity_panel(frame: &mut Frame, app: &App, area: Rect) {
    let block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border())
        .title(" Sparsity ");
    let split = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Min(5), Constraint::Length(3)])
        .split(block.inner(area));
    frame.render_widget(block, area);

    if let Some(cell) = app.current_matrix() {
        frame.render_widget(Sparsity::new(&cell.matrix), split[0]);
    }
    frame.render_widget(Paragraph::new(legend_lines()), split[1]);
}

fn draw_batch(frame: &mut Frame, app: &App, area: Rect) {
    let split = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Length(5), Constraint::Min(0)])
        .split(area);

    let matrices = app
        .matrices_to_export
        .iter()
        .map(|m| m.slug())
        .collect::<Vec<_>>()
        .join(", ");
    let header = Paragraph::new(vec![
        Line::from(vec![
            Span::styled("output dir: ", dim()),
            Span::raw(app.out_dir.display().to_string()),
        ]),
        Line::from(vec![
            Span::styled("matrices:   ", dim()),
            Span::raw(matrices),
            Span::raw("   "),
            Span::styled("rhs: ", dim()),
            Span::raw(format!("{:?}", app.rhs)),
            Span::raw("   "),
            Span::styled("scheme: ", dim()),
            Span::raw(format!("{:?}", app.scheme)),
        ]),
        Line::from(vec![
            Span::styled("queue:      ", dim()),
            Span::raw(format!(
                "{} cases (selected via Space in Browse, or current row)",
                app.batch_targets().len()
            )),
        ]),
    ])
    .block(
        Block::default()
            .borders(Borders::ALL)
            .border_type(BorderType::Rounded)
            .border_style(border())
            .title(" Batch export "),
    );
    frame.render_widget(header, split[0]);

    let body = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border())
        .title(" Progress ");
    let inner = body.inner(split[1]);
    frame.render_widget(body, split[1]);

    if app.batch.is_empty() {
        frame.render_widget(
            Paragraph::new(Line::from(Span::styled("press [e] to begin export", dim()))),
            inner,
        );
        return;
    }

    let row_h = 2u16;
    for (i, job) in app.batch.iter().enumerate() {
        let y = inner.y + i as u16 * row_h;
        if y + row_h > inner.y + inner.height {
            break;
        }
        let row = Rect::new(inner.x, y, inner.width, row_h);
        draw_batch_row(frame, job, row);
    }
}

fn draw_batch_row(frame: &mut Frame, job: &BatchJob, row: Rect) {
    let split = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(20), Constraint::Min(0)])
        .split(row);
    let label = Paragraph::new(Line::from(vec![Span::styled(
        format!(" {} ", job.case_name),
        Style::default().fg(T.fg),
    )]));
    frame.render_widget(label, split[0]);
    let (ratio, label_text, color) = match &job.progress {
        BatchProgress::Pending => (0.0, "queued".to_string(), T.fg_dim),
        &BatchProgress::Running(f) => (f, format!("{:>3}%", (f * 100.0) as i32), T.accent),
        BatchProgress::Done { files } => (1.0, format!("{files} files"), T.good),
        BatchProgress::Failed(msg) => (1.0, format!("FAIL: {msg}"), T.bad),
    };
    let gauge = Gauge::default()
        .gauge_style(Style::default().fg(color))
        .ratio(ratio)
        .label(label_text);
    frame.render_widget(gauge, split[1]);
}

fn draw_synth(frame: &mut Frame, app: &App, area: Rect) {
    let split = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
        .split(area);

    let mut lines = Vec::new();
    let s = &app.synth.spec;
    lines.push(synth_field_line(
        "topology",
        App::topology_label(s.topology).to_string(),
        app.synth.field == SynthField::Topology,
    ));
    lines.push(synth_field_line(
        "n",
        s.n.to_string(),
        app.synth.field == SynthField::N,
    ));
    lines.push(synth_field_line(
        "r/x",
        format!("{:.3}", s.r_over_x),
        app.synth.field == SynthField::ROverX,
    ));
    lines.push(synth_field_line(
        "mean x",
        format!("{:.3}", s.mean_x),
        app.synth.field == SynthField::MeanX,
    ));
    lines.push(synth_field_line(
        "seed",
        format!("0x{:X}", s.seed),
        app.synth.field == SynthField::Seed,
    ));
    lines.push(Line::raw(""));
    lines.push(Line::from(Span::styled(
        "g generates · e exports to out/",
        dim(),
    )));

    let form = Paragraph::new(lines).block(
        Block::default()
            .borders(Borders::ALL)
            .border_type(BorderType::Rounded)
            .border_style(border())
            .title(" Synth "),
    );
    frame.render_widget(form, split[0]);

    let preview_block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border())
        .title(" Preview ");
    if let Some(case) = &app.synth.generated {
        let view = powerio_matrix::IndexedNetwork::new(case);
        if let Ok(b) = powerio_matrix::build_bprime(&view, &powerio_matrix::BuildOptions::default())
        {
            let inner = preview_block.inner(split[1]);
            frame.render_widget(preview_block, split[1]);
            frame.render_widget(Sparsity::new(&b), inner);
        } else {
            frame.render_widget(
                Paragraph::new("(failed to build B')").block(preview_block),
                split[1],
            );
        }
    } else {
        frame.render_widget(
            Paragraph::new(Line::from(Span::styled("press [g] to generate", dim())))
                .block(preview_block),
            split[1],
        );
    }
}

fn synth_field_line(label: &str, value: String, focused: bool) -> Line<'static> {
    let marker = if focused { "▶ " } else { "  " };
    let style = if focused {
        highlight()
    } else {
        Style::default()
    };
    Line::from(vec![
        Span::styled(marker, Style::default().fg(T.accent_alt)),
        Span::styled(format!("{label:<10}"), dim()),
        Span::styled(value, style),
    ])
}

fn draw_log(frame: &mut Frame, app: &App, area: Rect) {
    let snap = app.log.snapshot();
    let visible = snap
        .iter()
        .rev()
        .take((area.height.saturating_sub(2)) as usize)
        .cloned()
        .collect::<Vec<_>>();
    let lines: Vec<Line> = visible
        .into_iter()
        .rev()
        .map(|s| {
            let style = if s.contains("ERROR") {
                bad()
            } else if s.contains("WARN") {
                warn()
            } else {
                Style::default().fg(T.fg)
            };
            Line::from(Span::styled(s, style))
        })
        .collect();
    let block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border())
        .title(" Log ");
    frame.render_widget(Paragraph::new(lines).block(block), area);
}

fn draw_help(frame: &mut Frame, _app: &App, area: Rect) {
    let block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(border())
        .title(" Help ")
        .title_alignment(Alignment::Center);
    let inner = block.inner(area);
    frame.render_widget(block, area);

    let lines = vec![
        Line::from(Span::styled("powerio TUI cheatsheet", title())),
        Line::raw(""),
        Line::raw("Browse"),
        Line::raw("  ↑/↓        move selection"),
        Line::raw("  Space      add/remove from batch queue"),
        Line::raw("  Enter      open Inspect for current case"),
        Line::raw("  b          enter Batch screen"),
        Line::raw("  g          enter Synthetic generator"),
        Line::raw("  R          re-scan data directory"),
        Line::raw(""),
        Line::raw("Inspect"),
        Line::raw("  Tab/Shift-Tab cycle matrix kind (B', B'', G, -B, LACPF)"),
        Line::raw("  s          toggle BX ↔ XB scheme (rebuilds matrices)"),
        Line::raw("  e          export current matrix to out_dir"),
        Line::raw("  Esc        back to Browse"),
        Line::raw(""),
        Line::raw("Batch"),
        Line::raw("  m          cycle which matrices to emit"),
        Line::raw("  r          cycle RHS strategy (none/random/injection)"),
        Line::raw("  e          start export"),
        Line::raw(""),
        Line::raw("Synth"),
        Line::raw("  ↑/↓        select field"),
        Line::raw("  ←/→        decrement/increment field"),
        Line::raw("  g          (re)generate the case"),
        Line::raw("  e          export to out_dir"),
        Line::raw(""),
        Line::raw("Global"),
        Line::raw("  ?          help (this screen)"),
        Line::raw("  q          quit"),
    ];
    frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), inner);
}
