//! Accessibility text-scaling factor.
//!
//! ltk's own runtime reads this key and repaints, but forge drives
//! `Canvas`/`UiSurface` from its own event loop and never calls `ltk::run`, so
//! nothing in this process would set the global. This watcher mirrors what
//! that runtime does: read the GNOME key once, then stream changes.

use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
use std::thread;

use calloop::channel::{self, Sender};
use calloop::LoopHandle;

use crate::forge::Forge;

const SCHEMA: &str = "org.gnome.desktop.interface";
const KEY: &str = "text-scaling-factor";

pub fn start_watcher(event_loop: &LoopHandle<'static, Forge>) {
    let (sender, channel) = channel::channel::<f32>();

    event_loop
        .insert_source(channel, |event, _, forge| {
            if let channel::Event::Msg(factor) = event {
                if (factor - ltk::text_scale()).abs() < f32::EPSILON {
                    return;
                }
                tracing::info!("text scaling factor: {factor}");
                ltk::set_text_scale(factor);
                forge.invalidate_text_caches();
                forge.force_redraw(true);
            }
        })
        .expect("text scale channel");

    thread::Builder::new()
        .name("forge-text-scale".into())
        .spawn(move || run_watcher(sender))
        .expect("text scale thread");
}

fn run_watcher(sender: Sender<f32>) {
    if let Some(factor) = read_current() {
        if sender.send(factor).is_err() {
            return;
        }
    }

    let Ok(mut child) = Command::new("gsettings")
        .args(["monitor", SCHEMA, KEY])
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
    else {
        return;
    };

    if let Some(stdout) = child.stdout.take() {
        for line in BufReader::new(stdout).lines().map_while(Result::ok) {
            if let Some(factor) = parse_factor(&line) {
                if sender.send(factor).is_err() {
                    break;
                }
            }
        }
    }

    let _ = child.kill();
}

fn read_current() -> Option<f32> {
    let out = Command::new("gsettings")
        .args(["get", SCHEMA, KEY])
        .output()
        .ok()?;
    if !out.status.success() {
        return None;
    }
    String::from_utf8_lossy(&out.stdout)
        .trim()
        .parse::<f32>()
        .ok()
        .filter(|f| *f > 0.0)
}

/// Each `gsettings monitor` line has the shape `text-scaling-factor: 1.25`.
fn parse_factor(line: &str) -> Option<f32> {
    line.rsplit([' ', ':'])
        .next()?
        .trim()
        .parse::<f32>()
        .ok()
        .filter(|f| *f > 0.0)
}
