package stmtcache

import (
	
)

// lruNode is a typed doubly-linked list node with freelist support.
type lruNode struct {
	sd   *pgconn.StatementDescription
	prev *lruNode
	next *lruNode
}

// LRUCache implements Cache with a Least Recently Used (LRU) cache.
type LRUCache struct {
	m    map[string]*lruNode
	head *lruNode

	tail     *lruNode
	len      int
	cap      int
	freelist *lruNode

	invalidStmts []*pgconn.StatementDescription
	invalidSet   map[string]struct{}
}

// NewLRUCache creates a new LRUCache. cap is the maximum size of the cache.
func ( int) *LRUCache {
	 := &lruNode{}
	 := &lruNode{}
	.next = 
	.prev = 

	return &LRUCache{
		cap:        ,
		m:          make(map[string]*lruNode, ),
		head:       ,
		tail:       ,
		invalidSet: make(map[string]struct{}),
	}
}

// Get returns the statement description for sql. Returns nil if not found.
func ( *LRUCache) ( string) *pgconn.StatementDescription {
	,  := .m[]
	if ! {
		return nil
	}
	.moveToFront()
	return .sd
}

// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache or
// sd.SQL has been invalidated and HandleInvalidated has not been called yet.
func ( *LRUCache) ( *pgconn.StatementDescription) {
	if .SQL == "" {
		panic("cannot store statement description with empty SQL")
	}

	if ,  := .m[.SQL];  {
		return
	}

	// The statement may have been invalidated but not yet handled. Do not readd it to the cache.
	if ,  := .invalidSet[.SQL];  {
		return
	}

	if .len == .cap {
		.invalidateOldest()
	}

	 := .allocNode()
	.sd = 
	.insertAfter(.head, )
	.m[.SQL] = 
	.len++
}

// Invalidate invalidates statement description identified by sql. Does nothing if not found.
func ( *LRUCache) ( string) {
	,  := .m[]
	if ! {
		return
	}
	delete(.m, )
	.invalidStmts = append(.invalidStmts, .sd)
	.invalidSet[] = struct{}{}
	.unlink()
	.len--
	.freeNode()
}

// InvalidateAll invalidates all statement descriptions.
func ( *LRUCache) () {
	for  := .head.next;  != .tail; {
		 := .next
		.invalidStmts = append(.invalidStmts, .sd)
		.invalidSet[.sd.SQL] = struct{}{}
		.freeNode()
		 = 
	}

	clear(.m)
	.head.next = .tail
	.tail.prev = .head
	.len = 0
}

// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
func ( *LRUCache) () []*pgconn.StatementDescription {
	return .invalidStmts
}

// RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
// never seen by the call to GetInvalidated.
func ( *LRUCache) () {
	.invalidStmts = .invalidStmts[:0]
	clear(.invalidSet)
}

// Len returns the number of cached prepared statement descriptions.
func ( *LRUCache) () int {
	return .len
}

// Cap returns the maximum number of cached prepared statement descriptions.
func ( *LRUCache) () int {
	return .cap
}

func ( *LRUCache) () {
	 := .tail.prev
	if  == .head {
		return
	}
	.invalidStmts = append(.invalidStmts, .sd)
	.invalidSet[.sd.SQL] = struct{}{}
	delete(.m, .sd.SQL)
	.unlink()
	.len--
	.freeNode()
}

// List operations - sentinel nodes eliminate nil checks

func ( *LRUCache) (,  *lruNode) {
	.prev = 
	.next = .next
	.next.prev = 
	.next = 
}

func ( *LRUCache) ( *lruNode) {
	.prev.next = .next
	.next.prev = .prev
}

func ( *LRUCache) ( *lruNode) {
	if .prev == .head {
		return
	}
	.unlink()
	.insertAfter(.head, )
}

// Node pool operations - reuse evicted nodes to avoid allocations

func ( *LRUCache) () *lruNode {
	if .freelist != nil {
		 := .freelist
		.freelist = .next
		.next = nil
		.prev = nil
		return 
	}
	return &lruNode{}
}

func ( *LRUCache) ( *lruNode) {
	.sd = nil
	.prev = nil
	.next = .freelist
	.freelist = 
}