use anyhow::{Context, Result};
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;

use crate::config::StatusSpec;
use crate::themes;
use crate::tmux;

fn segments_dir() -> Result<PathBuf> {
    let home = dirs::home_dir().context("no home directory")?;
    let dir = home.join(".sessionx").join("segments");
    std::fs::create_dir_all(&dir)?;
    Ok(dir)
}

/// Materialize a segment script idempotently. Returns its path.
fn write_segment(name: &str, command: &str) -> Result<PathBuf> {
    let dir = segments_dir()?;
    let path = dir.join(format!("{name}.sh"));
    let body = format!(
        "#!/usr/bin/env bash\n# auto-generated by sessionx — do not edit\nset -e\n{command}\n"
    );
    let needs_write = match std::fs::read_to_string(&path) {
        Ok(existing) => existing != body,
        Err(_) => true,
    };
    if needs_write {
        std::fs::write(&path, &body)?;
        let mut perm = std::fs::metadata(&path)?.permissions();
        perm.set_mode(0o755);
        std::fs::set_permissions(&path, perm)?;
    }
    Ok(path)
}

/// Apply the configured status bar to a session. Scope: per-session.
pub fn apply(session: &str, spec: &StatusSpec) -> Result<()> {
    if !spec.enabled {
        return Ok(());
    }

    // Materialize segment scripts first so format strings can reference them.
    for seg in &spec.segments {
        write_segment(&seg.name, &seg.command)?;
    }

    // Resolve theme defaults if a theme is named.
    let theme = match &spec.theme {
        Some(name) => Some(themes::load(name)?),
        None => None,
    };

    // Resolve id-style placeholders inside theme format strings.
    let win_style = spec.window_id_style.as_deref().unwrap_or("fsquare");
    let pane_style = spec.pane_id_style.as_deref().unwrap_or("super");
    themes::validate_id_style(win_style)?;
    themes::validate_id_style(pane_style)?;
    let resolved = theme.as_ref().map(|t| t.resolve(win_style, pane_style));

    // Materialize theme-provided helper scripts (idempotent).
    if let Some(t) = &theme {
        for (name, body) in &t.builtin_segments {
            write_segment(name, body)?;
        }
    }

    // Merge style: theme first, user overrides on top.
    let mut style = std::collections::BTreeMap::new();
    if let Some(t) = &theme {
        for (k, v) in &t.style {
            style.insert(k.clone(), v.clone());
        }
    }
    for (k, v) in &spec.style {
        // Normalize user keys to dashed form so they collide with theme keys.
        style.insert(k.replace('_', "-"), v.clone());
    }

    for (key, v) in &style {
        if key.starts_with("window-") || key.starts_with("pane-") {
            tmux::set_window_option_for_all(session, key, v)?;
        } else {
            tmux::set_option(session, key, v)?;
        }
    }

    let theme_left = resolved.as_ref().and_then(|r| r.left.as_deref());
    let theme_right = resolved.as_ref().and_then(|r| r.right.as_deref());
    let theme_win = resolved.as_ref().and_then(|r| r.window_format.as_deref());
    let theme_curwin = resolved
        .as_ref()
        .and_then(|r| r.current_window_format.as_deref());
    let theme_interval = theme.as_ref().and_then(|t| t.status_interval);

    if let Some(left) = spec.left.as_deref().or(theme_left) {
        tmux::set_option(session, "status-left", left)?;
        // Default left-length is 10 — usually too short for custom segments.
        tmux::set_option(session, "status-left-length", "200")?;
    }
    if let Some(right) = spec.right.as_deref().or(theme_right) {
        tmux::set_option(session, "status-right", right)?;
        tmux::set_option(session, "status-right-length", "200")?;
    }
    if let Some(fmt) = spec.window_format.as_deref().or(theme_win) {
        tmux::set_window_option_for_all(session, "window-status-format", fmt)?;
    }
    if let Some(fmt) = spec.current_window_format.as_deref().or(theme_curwin) {
        tmux::set_window_option_for_all(session, "window-status-current-format", fmt)?;
    }
    if let Some(interval) = spec.status_interval.or(theme_interval) {
        tmux::set_option(session, "status-interval", &interval.to_string())?;
    }

    Ok(())
}

/// Build SX_ICON_<NAME> env vars for hooks.
pub fn icon_env(spec: &StatusSpec) -> Vec<(String, String)> {
    spec.icons
        .iter()
        .map(|(k, v)| (format!("SX_ICON_{}", k.to_uppercase()), v.clone()))
        .collect()
}
