#include "PocketSphinxEngine.hpp"
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <fstream>

// Only include PocketSphinx headers if library is available
#ifdef HAVE_POCKETSPHINX
#include <pocketsphinx.h>
#endif

namespace celuna {

PocketSphinxEngine::PocketSphinxEngine(const std::string& modelPath,
                                       const std::vector<std::string>& keywords)
    : m_modelPath(modelPath)
    , m_keywords(keywords)
{
    m_logger = spdlog::get("PocketSphinx");
    if (!m_logger) {
        m_logger = spdlog::stdout_color_mt("PocketSphinx");
    }

    m_keywordMode = !keywords.empty();
    m_available = initialize();

    if (m_available) {
        m_logger->info("PocketSphinx STT initialized: model={}, keyword_mode={}",
                      modelPath, m_keywordMode);
    } else {
        m_logger->warn("PocketSphinx not available (library not installed or model missing)");
    }
}

PocketSphinxEngine::~PocketSphinxEngine() {
    cleanup();
}

bool PocketSphinxEngine::initialize() {
#ifdef HAVE_POCKETSPHINX
    // Check if model directory exists
    std::ifstream modelCheck(m_modelPath + "/mdef");
    if (!modelCheck.good()) {
        m_logger->error("PocketSphinx model not found at: {}", m_modelPath);
        return false;
    }

    // Create configuration
    m_config = cmd_ln_init(nullptr, ps_args(), TRUE,
                          "-hmm", m_modelPath.c_str(),
                          "-dict", (m_modelPath + "/cmudict-en-us.dict").c_str(),
                          "-logfn", "/dev/null",  // Suppress verbose logging
                          nullptr);

    if (!m_config) {
        m_logger->error("Failed to create PocketSphinx config");
        return false;
    }

    // Create decoder
    m_decoder = ps_init(m_config);
    if (!m_decoder) {
        m_logger->error("Failed to initialize PocketSphinx decoder");
        cmd_ln_free_r(m_config);
        m_config = nullptr;
        return false;
    }

    // If keyword mode, set up keyword spotting
    if (m_keywordMode) {
        setKeywords(m_keywords, m_keywordThreshold);
    }

    return true;
#else
    m_logger->warn("PocketSphinx support not compiled (HAVE_POCKETSPHINX not defined)");
    return false;
#endif
}

void PocketSphinxEngine::cleanup() {
#ifdef HAVE_POCKETSPHINX
    if (m_decoder) {
        ps_free(m_decoder);
        m_decoder = nullptr;
    }
    if (m_config) {
        cmd_ln_free_r(m_config);
        m_config = nullptr;
    }
#endif
}

void PocketSphinxEngine::setKeywords(const std::vector<std::string>& keywords, float threshold) {
    m_keywords = keywords;
    m_keywordThreshold = threshold;
    m_keywordMode = !keywords.empty();

#ifdef HAVE_POCKETSPHINX
    if (!m_decoder || keywords.empty()) {
        return;
    }

    // Build keyword string (format: "keyword /threshold/\n")
    std::string keywordStr;
    for (const auto& kw : keywords) {
        keywordStr += kw + " /1e-" + std::to_string(int(threshold * 100)) + "/\n";
    }

    // Set keyword spotting mode
    ps_set_kws(m_decoder, "keywords", keywordStr.c_str());
    ps_set_search(m_decoder, "keywords");

    m_logger->info("PocketSphinx keyword mode enabled: {} keywords, threshold={}",
                  keywords.size(), threshold);
#endif
}

std::string PocketSphinxEngine::processAudioData(const int16_t* audioData, size_t numSamples) {
#ifdef HAVE_POCKETSPHINX
    if (!m_decoder) {
        return "";
    }

    // Start utterance
    ps_start_utt(m_decoder);

    // Process audio
    ps_process_raw(m_decoder, audioData, numSamples, FALSE, FALSE);

    // End utterance
    ps_end_utt(m_decoder);

    // Get hypothesis
    const char* hyp = ps_get_hyp(m_decoder, nullptr);
    if (hyp) {
        std::string result(hyp);
        m_logger->debug("PocketSphinx recognized: {}", result);
        return result;
    }

    return "";
#else
    return "";
#endif
}

std::string PocketSphinxEngine::transcribe(const std::vector<float>& audioData) {
    if (!m_available || audioData.empty()) {
        return "";
    }

    // Convert float samples to int16
    std::vector<int16_t> int16Data(audioData.size());
    for (size_t i = 0; i < audioData.size(); ++i) {
        float sample = audioData[i];
        // Clamp to [-1.0, 1.0] and convert to int16
        if (sample > 1.0f) sample = 1.0f;
        if (sample < -1.0f) sample = -1.0f;
        int16Data[i] = static_cast<int16_t>(sample * 32767.0f);
    }

    return processAudioData(int16Data.data(), int16Data.size());
}

std::string PocketSphinxEngine::transcribeFile(const std::string& filePath) {
    if (!m_available) {
        return "";
    }

    m_logger->info("PocketSphinx transcribing file: {}", filePath);

    // For file transcription, we'd need to:
    // 1. Read the audio file (wav/raw)
    // 2. Convert to int16 PCM
    // 3. Call processAudioData
    //
    // For now, return empty (file I/O requires additional dependencies)
    m_logger->warn("PocketSphinx file transcription not yet implemented");
    return "";
}

void PocketSphinxEngine::setLanguage(const std::string& language) {
    m_language = language;
    m_logger->info("PocketSphinx language set to: {}", language);
    // Note: PocketSphinx requires different acoustic models for different languages
    // Would need to reinitialize with appropriate model path
}

bool PocketSphinxEngine::isAvailable() const {
    return m_available;
}

std::string PocketSphinxEngine::getEngineName() const {
    return "pocketsphinx";
}

} // namespace celuna
