// Package conpty implements the Windows ConPTY runtime adapter for agent sessions.
// This file contains the OS-agnostic binary framing protocol codec used by the
// named-pipe protocol between pty-host.js and this Go client.
//
// Frame layout: [1-byte type][4-byte big-endian length][payload]
package conpty

import (
	"encoding/binary"
	"fmt"
	"math"
)

// Message type constants. Values must match pty-host.ts MSG_* constants exactly.
const (
	MsgTerminalData  byte = 0x01 // host -> client: raw PTY output
	MsgTerminalInput byte = 0x02 // client -> host: raw keystrokes
	MsgResize        byte = 0x03 // client -> host: JSON {cols, rows}
	MsgGetOutputReq  byte = 0x04 // client -> host: JSON {lines}
	MsgGetOutputRes  byte = 0x05 // host -> client: UTF-8 text
	MsgStatusReq     byte = 0x06 // client -> host: empty
	MsgStatusRes     byte = 0x07 // host -> client: JSON {alive, pid, exitCode?}
	MsgKillReq       byte = 0x08 // client -> host: empty
)

// JSON payload structs shared with later tasks (kept minimal).

// ResizePayload is the JSON body for MsgResize.
type ResizePayload struct {
	Cols int `json:"cols"`
	Rows int `json:"rows"`
}

// StatusPayload is the JSON body for MsgStatusRes.
type StatusPayload struct {
	Alive    bool `json:"alive"`
	PID      int  `json:"pid"`
	ExitCode *int `json:"exitCode,omitempty"`
}

// GetOutputReq is the JSON body for MsgGetOutputReq.
type GetOutputReq struct {
	Lines int `json:"lines"`
}

// EncodeMessage encodes a single frame into the binary protocol format.
// It allocates a fresh slice of exactly 5+len(payload) bytes.
// Returns an error if the payload exceeds the 4-byte length field capacity.
func EncodeMessage(msgType byte, payload []byte) ([]byte, error) {
	n := len(payload)
	if n > math.MaxUint32 {
		return nil, fmt.Errorf("conpty: payload too large (%d bytes, max %d)", n, math.MaxUint32)
	}
	payloadLen := uint32(n) // safe: n <= math.MaxUint32 checked above
	frame := make([]byte, 5+n)
	frame[0] = msgType
	binary.BigEndian.PutUint32(frame[1:5], payloadLen)
	copy(frame[5:], payload)
	return frame, nil
}

// MessageParser is a streaming parser for the binary framing protocol.
// It accumulates arbitrary-sized chunks from a pipe/socket stream and fires
// onMessage exactly once per complete frame, regardless of chunk boundaries.
// Safe to call Feed from a single goroutine; not concurrency-safe itself.
type MessageParser struct {
	buf       []byte
	onMessage func(msgType byte, payload []byte)
}

// NewMessageParser returns a parser that calls onMessage for each complete frame.
// onMessage receives a COPY of the payload so callers may retain it safely.
func NewMessageParser(onMessage func(msgType byte, payload []byte)) *MessageParser {
	return &MessageParser{onMessage: onMessage}
}

// Feed appends chunk to the internal buffer and dispatches all complete frames.
// It matches the semantics of MessageParser.feed in pty-host.ts exactly:
// arbitrary chunk boundaries and multiple frames per chunk are both handled.
func (p *MessageParser) Feed(chunk []byte) {
	p.buf = append(p.buf, chunk...)

	for len(p.buf) >= 5 {
		payloadLen := binary.BigEndian.Uint32(p.buf[1:5])
		frameLen := 5 + int(payloadLen)
		if len(p.buf) < frameLen {
			break
		}

		msgType := p.buf[0]
		// ponytail: explicit copy so callers that retain the slice are not
		// corrupted when p.buf grows/reallocates on a later Feed call.
		payload := make([]byte, payloadLen)
		copy(payload, p.buf[5:frameLen])

		p.buf = p.buf[frameLen:]
		p.onMessage(msgType, payload)
	}
}
