//! Dünner Adapter um die `bladeink`-Crate, gekoppelt an unseren `kv::Store`.
//!
//! Bei jedem `cont()` läuft ein bidirektionaler Sync:
//!   - vor `continue`: KV-Werte ins `variables_state` der Story spiegeln
//!     (Skript sieht aktuelle Spielvariablen)
//!   - nach `continue`: alle KV-Keys, die das Skript kennt, zurücklesen
//!     (Ink-`~ var = …`-Writes landen im KV)
//!
//! Variablen die der KV kennt, das Skript aber nicht als `VAR` deklariert,
//! lehnt bladeink beim `set_variable` ab — wir ignorieren das still.
//! Variablen die nur im Skript existieren, werden beim Read-Back ignoriert
//! (KV bleibt minimal: nur was Rust kennt).

use std::fs::read_to_string;

use bladeink::story::Story as BladeStory;

use crate::engine::kv::Store;

pub enum StoryState {
    Text(String),
    Choice { prompt: String, options: Vec<String> },
    End,
}

pub struct Story {
    inner: BladeStory,
    tags:  Vec<String>,
}

impl Story {
    pub fn load(path: &str, kv: &Store) -> Self {
        let src = read_to_string(path)
            .unwrap_or_else(|e| panic!("ink load {}: {}", path, e));
        let inner = BladeStory::new(&src)
            .unwrap_or_else(|e| panic!("ink parse {}: {:?}", path, e));
        let mut s = Self { inner, tags: Vec::new() };
        s.sync_from_kv(kv);
        s
    }

    pub fn take_tags(&mut self) -> Vec<String> { std::mem::take(&mut self.tags) }

    pub fn cont(&mut self, kv: &mut Store) -> StoryState {
        self.sync_from_kv(kv);

        // continue_maximally ruft intern reset_output() vor jedem cont() —
        // damit überschriebe der Tag der letzten Zeile alle vorherigen.
        // Wir loopen selbst und sammeln Tags nach jedem Schritt ein, sonst
        // gehen z.B. `# set has_key true` am Knoten-Anfang bei mehrzeiligem
        // Text verloren.
        let mut text = String::new();
        while self.inner.can_continue() {
            let chunk = self.inner.cont()
                .unwrap_or_else(|e| panic!("ink cont: {:?}", e));
            text.push_str(&chunk);
            if let Ok(t) = self.inner.get_current_tags() {
                self.tags.extend(t);
            }
        }
        let text = text.trim().to_string();

        self.sync_to_kv(kv);

        let choices = self.inner.get_current_choices();
        if !choices.is_empty() {
            let options = choices.iter().map(|c| c.text.clone()).collect();
            return StoryState::Choice { prompt: text, options };
        }
        if !text.is_empty() { return StoryState::Text(text); }
        StoryState::End
    }

    pub fn choose(&mut self, i: usize) {
        self.inner.choose_choice_index(i)
            .unwrap_or_else(|e| panic!("ink choose {}: {:?}", i, e));
    }

    fn sync_from_kv(&mut self, kv: &Store) {
        for (name, value) in kv {
            // set_variable schlägt fehl wenn der Name kein deklariertes
            // VAR im Skript ist — still ignorieren.
            let _ = self.inner.set_variable(name, value);
        }
    }

    fn sync_to_kv(&self, kv: &mut Store) {
        let keys: Vec<String> = kv.keys().cloned().collect();
        for name in keys {
            if let Some(v) = self.inner.get_variable(&name) {
                kv.insert(name, v);
            }
        }
    }
}
