use ratatui::{
    DefaultTerminal, Frame,
    crossterm::{
        self,
        event::{Event, KeyCode, KeyModifiers},
    },
    layout::{Constraint, Layout},
    style::Stylize,
    widgets::Paragraph,
};
use serde::Deserialize;

#[derive(Deserialize)]
struct Story {
    title: String,
    url: Option<String>,
    score: u32,
    by: String,
}

struct State {
    stories: Vec<Story>,
    selected: usize,
    show_help: bool,
}

fn main() -> color_eyre::Result<()> {
    color_eyre::install()?;
    let stories: Vec<Story> = fetch_hn();
    let mut state = State {
        stories,
        selected: 0,
        show_help: true,
    };
    ratatui::run(|terminal| app(terminal, &mut state))?;
    Ok(())
}

fn app(terminal: &mut DefaultTerminal, state: &mut State) -> std::io::Result<()> {
    loop {
        terminal.draw(|frame| render(frame, state))?;
        match crossterm::event::read()? {
            Event::Key(key) => {
                if key.code == KeyCode::Char('q')
                    || ((key.code == KeyCode::Char('c') || key.code == KeyCode::Char('d'))
                        && key.modifiers.contains(KeyModifiers::CONTROL))
                {
                    break Ok(());
                }
                if key.code == KeyCode::Char('k') {
                    if state.selected != 0 {
                        state.selected -= 1;
                    }
                }
                if key.code == KeyCode::Char('j') {
                    if state.stories.get(state.selected + 1).is_some() {
                        state.selected += 1;
                    }
                }
                if key.code == KeyCode::Char('o') && state.stories[state.selected].url.is_some() {
                    open::that(state.stories[state.selected].url.as_deref().unwrap())?;
                }
                if key.code == KeyCode::Char('?') {
                    state.show_help = !state.show_help;
                }
            }
            _ => {}
        }
    }
}

fn render(frame: &mut Frame, state: &mut State) {
    if state.show_help {
        let outer_layout = Layout::default()
            .direction(ratatui::layout::Direction::Horizontal)
            .constraints(vec![
                Constraint::Percentage(80),
                Constraint::Percentage(10),
                Constraint::Percentage(10),
            ])
            .split(frame.area());

        frame.render_widget(Paragraph::new("Hacker News TUI\n Help: \n '?' to hide/show help. \n 'j'/'k' to scroll. \n 'o' to open url. \n 'q' to quit.").cyan(), outer_layout[1]);
    }

    let outer_layout = Layout::default()
        .direction(ratatui::layout::Direction::Horizontal)
        .constraints(vec![Constraint::Percentage(100)])
        .split(frame.area());

    let inner_layout = Layout::default()
        .direction(ratatui::layout::Direction::Vertical)
        .constraints(vec![Constraint::Percentage(10), Constraint::Percentage(5), Constraint::Percentage(20), Constraint::Percentage(80)])
        .split(outer_layout[0]);

    let story = &state.stories[state.selected];

    frame.render_widget(
        format!(
            "  {}, by {}. (HN Score: {})",
            story.title, story.by, story.score
        ),
        inner_layout[1],
    );

    frame.render_widget(
        format!("  {}", story.url.as_deref().unwrap_or("(no url)")),
        inner_layout[2],
    );
}

fn fetch_hn() -> Vec<Story> {
    let client = reqwest::blocking::Client::new();
    println!("Fetching Hacker News stories... please wait :)");
    let ids: Vec<u64> = client
        .get("https://hacker-news.firebaseio.com/v0/topstories.json")
        .send()
        .expect("Failed to fetch from hacker news")
        .json()
        .expect("Failed to parse story ids");

    let mut stories: Vec<Story> = vec![];

    for (_, id) in ids.iter().take(20).enumerate() {
        let url = format!("https://hacker-news.firebaseio.com/v0/item/{id}.json");
        let story: Story = client
            .get(&url)
            .send()
            .expect("Failed to fetch story")
            .json()
            .expect("Failed to parse story");

        stories.push(story);
    }

    stories
}
