use std::io::Write;

use busybar_render::{MIRROR_RASTER, Mirror, Raster};
use busylib::TungsteniteWsTransport;
use busylib::proto::bsb_frame::{Frame as StreamedFrame, Screen};
use busylib::proto::bsb_state::State;
use busylib::proto::bsb_state::state_update::State as Update;

use crate::cli::Context;
use crate::error::Result;
use crate::reporter::UnsupportedEvent;
use crate::types::frame::{Frame, FrameError};
use crate::types::screen_arg::ScreenArg;

pub async fn run(context: &Context, screen: ScreenArg, no_screen_raster: bool) -> Result<()> {
    if let Some(event) = UnsupportedEvent::output_format(
        "mirror",
        "can't mirror frames from the device visually with json output =(",
        context.output_format,
    ) {
        return context.reporter.report(event).map_err(Into::into);
    }

    let screen = Screen::from(screen);
    let raster = (!no_screen_raster && screen == Screen::Front).then_some(MIRROR_RASTER);

    let transport = TungsteniteWsTransport::new();
    let mut stream = context.client.streaming().status_ws(&transport).await?;

    let mut mirror = Mirror::new(std::io::stdout().lock());
    mirror.enter()?;

    loop {
        tokio::select! {
            _ = tokio::signal::ctrl_c() => break,
            message = stream.next() => match message {
                Some(message) => self::draw(&mut mirror, message?, screen, raster)?,
                None => break,
            },
        }
    }

    mirror.leave()?;
    stream.close().await?;

    Ok(())
}

fn draw<W: Write>(
    mirror: &mut Mirror<W>,
    state: State,
    screen: Screen,
    raster: Option<Raster>,
) -> Result<()> {
    for update in state.updates {
        let Some(Update::Frame(frame)) = update.state else {
            continue;
        };

        if frame.screen() != screen {
            continue;
        }

        match self::decode(&frame, raster) {
            Ok(frame) => mirror.draw(frame.image())?,
            Err(error) => mirror.notice(&error.to_string())?,
        }
    }

    Ok(())
}

fn decode(frame: &StreamedFrame, raster: Option<Raster>) -> std::result::Result<Frame, FrameError> {
    let frame = Frame::from_streamed(frame)?;

    match raster {
        Some(raster) => frame.with_raster(raster),
        None => Ok(frame),
    }
}