//! `Runtime` implementation for embedded PHP.
//!
//! PHP module is initialized once. Worker threads attach to it
//! and only manage per-request lifecycle.

use anyhow::Result;
use async_trait::async_trait;
use folk_core::runtime::{Runtime, WorkerHandle};
use tracing::info;

use crate::handle::EmbedWorkerHandle;
use crate::php::PhpInstance;
use crate::worker::spawn_worker_thread;

/// Configuration for the embed runtime.
#[derive(Debug, Clone)]
pub struct EmbedConfig {
    /// Optional PHP bootstrap script to load on each worker.
    pub script: Option<String>,
}

/// Embedded PHP runtime — spawns worker threads instead of processes.
///
/// PHP module is initialized once in `new()`. Worker threads call
/// `PhpInstance::attach()` to share the module without re-init.
pub struct EmbedRuntime {
    config: EmbedConfig,
    /// Keeps the module alive — dropped only when `EmbedRuntime` is dropped.
    _php_module: PhpInstance,
}

impl EmbedRuntime {
    /// Initialize the embed runtime.
    ///
    /// Calls `folk_sapi_init()` once — sets up PHP module, extensions,
    /// `OPcache`, signal handlers. Worker threads will attach to this.
    ///
    /// PHP module startup is run in a dedicated OS thread (not a tokio
    /// worker thread) to give PHP a clean stack and standard thread state.
    pub fn new(config: EmbedConfig) -> Result<Self> {
        info!("initializing PHP embed module");
        let php = PhpInstance::boot_custom_sapi()?;
        info!("PHP embed module ready");

        Ok(Self {
            config,
            _php_module: php,
        })
    }
}

#[async_trait]
impl Runtime for EmbedRuntime {
    async fn spawn(&self) -> Result<Box<dyn WorkerHandle>> {
        let (thread, cmd_tx, task_resp_rx, control_rx, worker_id) =
            spawn_worker_thread(self.config.script.clone());

        Ok(Box::new(EmbedWorkerHandle::new(
            worker_id,
            cmd_tx,
            task_resp_rx,
            control_rx,
            thread,
        )))
    }
}
