// terminal_panel_windows.cpp — backend ConPTY para Windows.
// Compilado solo en plataformas Windows (_WIN32).
//
// Implementacion: CreatePseudoConsole (ConPTY, Windows 10 v1809+) +
// CreateProcess + ReadFile en thread de lectura.
//
// Si ConPTY no está disponible (Windows < 10 v1809), cae a un stub que
// reporta error y deja is_open() == false.
//
// TODO(0132): fallback CreatePipe sin PTY para Windows < v1809.

#ifdef _WIN32

#include "viz/terminal_panel/terminal_panel.h"
#include "core/logger.h"

// Incluir Windows.h con defines minimos para evitar conflictos con ImGui.
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>

// ConPTY: disponible en Windows SDK >= 17763 (v1809).
// Si el SDK no tiene ConPTY, definimos stubs minimos para que compile.
#if defined(NTDDI_WIN10_RS5) && NTDDI_VERSION >= NTDDI_WIN10_RS5
#  define FN_CONPTY_AVAILABLE 1
#  include <consoleapi3.h>
#  include <processthreadsapi.h>
#else
#  define FN_CONPTY_AVAILABLE 0
   // Stub para evitar errores de compilacion en SDKs viejos.
   typedef VOID* HPCON;
#endif

#include <string>

namespace fn_term {

namespace {

std::string default_shell_windows() {
    // Preferir PowerShell si está disponible; fallback a cmd.exe.
    char buf[MAX_PATH] = {};
    if (ExpandEnvironmentStringsA("%COMSPEC%", buf, sizeof(buf)) > 0 && buf[0] != '\0')
        return buf;
    return "cmd.exe";
}

#if FN_CONPTY_AVAILABLE

// Thread de lectura: lee del pipe de salida del ConPTY en bloques.
DWORD WINAPI reader_thread_fn(LPVOID param) {
    auto* panel = static_cast<TerminalPanel*>(param);
    char buf[4096];
    DWORD bytes_read = 0;
    while (panel->reader_running.load()) {
        BOOL ok = ReadFile(static_cast<HANDLE>(panel->pipe_read),
                           buf, sizeof(buf), &bytes_read, nullptr);
        if (ok && bytes_read > 0) {
            process_output(*panel, buf, static_cast<size_t>(bytes_read));
        } else {
            DWORD err = GetLastError();
            if (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) break;
            if (!ok) {
                fn_log::log_error("terminal_panel: ReadFile error %lu", err);
                break;
            }
        }
    }
    // Recolectar código de salida.
    if (panel->proc_handle) {
        DWORD exit_code = 0;
        GetExitCodeProcess(static_cast<HANDLE>(panel->proc_handle), &exit_code);
        panel->exit_code = static_cast<int>(exit_code);
    }
    panel->process_exited.store(true);
    panel->reader_running.store(false);
    return 0;
}

#endif // FN_CONPTY_AVAILABLE

} // namespace

void open(TerminalPanel& panel) {
    if (panel.is_open()) return;

#if !FN_CONPTY_AVAILABLE
    fn_log::log_error("terminal_panel: ConPTY not available on this Windows SDK version");
    // TODO(0132): fallback a CreatePipe sin PTY
    return;
#else
    std::string sh = panel.shell.empty() ? default_shell_windows() : panel.shell;

    // Crear dos pares de pipes: una para PTY→app (lectura) y otra para app→PTY (escritura).
    HANDLE hPipeIn_Read  = nullptr; // PTY lee desde aqui (stdin del proceso hijo)
    HANDLE hPipeIn_Write = nullptr; // app escribe aqui
    HANDLE hPipeOut_Read = nullptr; // app lee desde aqui (stdout del proceso hijo)
    HANDLE hPipeOut_Write= nullptr; // PTY escribe aqui

    SECURITY_ATTRIBUTES sa;
    sa.nLength              = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle       = FALSE;
    sa.lpSecurityDescriptor = nullptr;

    if (!CreatePipe(&hPipeIn_Read, &hPipeIn_Write, &sa, 0) ||
        !CreatePipe(&hPipeOut_Read, &hPipeOut_Write, &sa, 0)) {
        fn_log::log_error("terminal_panel: CreatePipe failed: %lu", GetLastError());
        return;
    }

    // Crear ConPTY.
    COORD consoleSize;
    consoleSize.X = 80;
    consoleSize.Y = 24;
    HPCON hPC = nullptr;
    HRESULT hr = CreatePseudoConsole(consoleSize, hPipeIn_Read, hPipeOut_Write, 0, &hPC);
    if (FAILED(hr)) {
        fn_log::log_error("terminal_panel: CreatePseudoConsole failed: hr=0x%08lX", hr);
        CloseHandle(hPipeIn_Read);
        CloseHandle(hPipeIn_Write);
        CloseHandle(hPipeOut_Read);
        CloseHandle(hPipeOut_Write);
        return;
    }

    // Los extremos del ConPTY (hPipeIn_Read + hPipeOut_Write) ya no los necesitamos.
    CloseHandle(hPipeIn_Read);
    CloseHandle(hPipeOut_Write);

    // Preparar STARTUPINFOEX con el ConPTY.
    SIZE_T attrListSize = 0;
    InitializeProcThreadAttributeList(nullptr, 1, 0, &attrListSize);
    auto* attrList = static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
        HeapAlloc(GetProcessHeap(), 0, attrListSize));
    if (!attrList || !InitializeProcThreadAttributeList(attrList, 1, 0, &attrListSize)) {
        fn_log::log_error("terminal_panel: InitializeProcThreadAttributeList failed");
        ClosePseudoConsole(hPC);
        CloseHandle(hPipeIn_Write);
        CloseHandle(hPipeOut_Read);
        if (attrList) HeapFree(GetProcessHeap(), 0, attrList);
        return;
    }
    UpdateProcThreadAttribute(attrList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
                               hPC, sizeof(HPCON), nullptr, nullptr);

    STARTUPINFOEXA siEx = {};
    siEx.StartupInfo.cb      = sizeof(STARTUPINFOEXA);
    siEx.lpAttributeList     = attrList;

    PROCESS_INFORMATION pi = {};
    // cmd es la cadena de comando (mutable, CreateProcessA la modifica en algunos casos).
    std::string cmd = sh;
    if (!CreateProcessA(nullptr, &cmd[0], nullptr, nullptr, FALSE,
                        EXTENDED_STARTUPINFO_PRESENT, nullptr,
                        panel.cwd.empty() ? nullptr : panel.cwd.c_str(),
                        &siEx.StartupInfo, &pi)) {
        fn_log::log_error("terminal_panel: CreateProcess failed: %lu", GetLastError());
        DeleteProcThreadAttributeList(attrList);
        HeapFree(GetProcessHeap(), 0, attrList);
        ClosePseudoConsole(hPC);
        CloseHandle(hPipeIn_Write);
        CloseHandle(hPipeOut_Read);
        return;
    }

    // El thread handle del hijo no lo necesitamos.
    CloseHandle(pi.hThread);

    DeleteProcThreadAttributeList(attrList);
    HeapFree(GetProcessHeap(), 0, attrList);

    panel.pty_handle   = static_cast<void*>(hPC);
    panel.pipe_read    = static_cast<void*>(hPipeOut_Read);
    panel.pipe_write   = static_cast<void*>(hPipeIn_Write);
    panel.proc_handle  = static_cast<void*>(pi.hProcess);
    panel.process_exited.store(false);
    panel.reader_running.store(true);

    // Arrancar el reader thread via CreateThread (evitamos std::thread con WINAPI).
    HANDLE hThread = CreateThread(nullptr, 0, reader_thread_fn, &panel, 0, nullptr);
    if (!hThread) {
        fn_log::log_error("terminal_panel: CreateThread failed: %lu", GetLastError());
        // No fatal — el panel queda en estado parcial; close() limpiará.
    } else {
        // Convertir el HANDLE a std::thread via native_handle trick no es portable.
        // Para integración con std::thread::join(), usamos un wrapper.
        // En v1: detachamos el thread y usamos el atomic reader_running como señal.
        CloseHandle(hThread);
        // TODO(0132): migrar a std::thread para poder join() correctamente.
    }

    fn_log::log_info("terminal_panel: opened shell '%s' pid=%lu",
                     sh.c_str(), static_cast<unsigned long>(pi.dwProcessId));
#endif // FN_CONPTY_AVAILABLE
}

void send(TerminalPanel& panel, const std::string& text) {
#if !FN_CONPTY_AVAILABLE
    (void)panel; (void)text;
#else
    if (!panel.is_open() || panel.readonly || text.empty()) return;
    DWORD written = 0;
    WriteFile(static_cast<HANDLE>(panel.pipe_write),
              text.c_str(), static_cast<DWORD>(text.size()), &written, nullptr);
#endif
}

void close(TerminalPanel& panel) {
    panel.reader_running.store(false);

#if FN_CONPTY_AVAILABLE
    if (panel.pipe_write) {
        CloseHandle(static_cast<HANDLE>(panel.pipe_write));
        panel.pipe_write = nullptr;
    }
    if (panel.pipe_read) {
        CloseHandle(static_cast<HANDLE>(panel.pipe_read));
        panel.pipe_read = nullptr;
    }
    if (panel.proc_handle) {
        TerminateProcess(static_cast<HANDLE>(panel.proc_handle), 0);
        WaitForSingleObject(static_cast<HANDLE>(panel.proc_handle), 500);
        CloseHandle(static_cast<HANDLE>(panel.proc_handle));
        panel.proc_handle = nullptr;
    }
    if (panel.pty_handle) {
        ClosePseudoConsole(static_cast<HPCON>(panel.pty_handle));
        panel.pty_handle = nullptr;
    }
#endif

    // Esperar al reader thread si está joinable.
    if (panel.reader_thread.joinable()) panel.reader_thread.join();

    fn_log::log_info("terminal_panel: closed (windows)");
}

} // namespace fn_term

#endif // _WIN32
