// Package testmark v2 provides a clean, easy-to-use API for working with testmark documents.
package testmark

import (
	"flag"
	"strings"
)

// bytesLike represents basic types that can be converted to []byte for content.
type bytesLike interface {
	~[]byte | ~string
}

// Global configuration
var defaultRegenFlag = flag.Bool("testmark.regen", false, "regenerate test fixtures")

// ShouldRegen returns whether fixture regeneration is enabled.
func ShouldRegen() bool {
	return *defaultRegenFlag
}

// Internal type for positioning info
type hunkInfo struct {
	lineStart int
	lineEnd   int
	hunk      *Hunk
}

// Hunk represents a single data block within a testmark document.
type Hunk struct {
	name       string
	infoString string
	body       []byte
}

// Document represents a parsed testmark document.
type Document struct {
	original     []byte
	lines        [][]byte
	hunks        []hunkInfo
	hunksByName  map[string]*Hunk
	dirIndex     *DirEntry
	patchAccum   *PatchAccumulator
	autoPatching bool
}

// DirEntry represents a directory-like entry in the testmark document hierarchy.
type DirEntry struct {
	name            string
	path            string
	hunk            *Hunk
	children        map[string]*DirEntry
	childrenOrdered []*DirEntry
}

// PatchAccumulator collects patches for document modification.
type PatchAccumulator struct {
	patches []Hunk
}

// Hunk accessor methods

// Name returns the hunk's name.
func (h *Hunk) Name() string {
	return h.name
}

// InfoString returns the code block's info string (language hint).
func (h *Hunk) InfoString() string {
	return h.infoString
}

// Body returns a copy of the hunk's body bytes.
func (h *Hunk) Body() []byte {
	body := make([]byte, len(h.body))
	copy(body, h.body)
	return body
}

// String returns the body as a string.
func (h *Hunk) String() string {
	return string(h.body)
}

// Document accessor methods

// HunkByName returns a hunk by name, or nil if not found.
func (d *Document) HunkByName(name string) *Hunk {
	return d.hunksByName[name]
}

// Count returns the number of hunks in the document.
func (d *Document) Count() int {
	return len(d.hunks)
}

// AllHunks returns all hunks as a slice in document order.
// Convenient alternative to iterating with Hunks().
func (d *Document) AllHunks() []*Hunk {
	var result []*Hunk
	for hunk := range d.Hunks() {
		result = append(result, hunk)
	}
	return result
}

// HasHunk returns true if a hunk with the given name exists.
func (d *Document) HasHunk(name string) bool {
	return d.HunkByName(name) != nil
}

// MustHunkByName returns a hunk by name, panicking if not found.
// Useful for tests where you know the hunk should exist.
func (d *Document) MustHunkByName(name string) *Hunk {
	hunk := d.HunkByName(name)
	if hunk == nil {
		panic("hunk not found: " + name)
	}
	return hunk
}

// HunkNames returns the names of all hunks in document order.
func (d *Document) HunkNames() []string {
	var names []string
	for hunk := range d.Hunks() {
		names = append(names, hunk.Name())
	}
	return names
}

// Root returns the root directory entry for hierarchical access.
func (d *Document) Root() *DirEntry {
	return d.dirIndex
}

// DirEntry accessor methods

// Name returns the entry's name (just the last path segment).
func (e *DirEntry) Name() string {
	return e.name
}

// Path returns the full path of this entry.
func (e *DirEntry) Path() string {
	return e.path
}

// Hunk returns the hunk associated with this entry, or nil.
func (e *DirEntry) Hunk() *Hunk {
	return e.hunk
}

// Child returns a child entry by name, or nil if not found.
func (e *DirEntry) Child(name string) *DirEntry {
	return e.children[name]
}

// IsDir returns true if this entry has children.
func (e *DirEntry) IsDir() bool {
	return len(e.children) > 0
}

// IsFile returns true if this entry has an associated hunk.
func (e *DirEntry) IsFile() bool {
	return e.hunk != nil
}

// AllChildren returns all child entries as a slice.
// Convenient alternative to iterating with Children().
func (e *DirEntry) AllChildren() []*DirEntry {
	var result []*DirEntry
	for child := range e.Children() {
		result = append(result, child)
	}
	return result
}

// HasChild returns true if a child with the given name exists.
func (e *DirEntry) HasChild(name string) bool {
	return e.Child(name) != nil
}

// ChildNames returns the names of all child entries.
func (e *DirEntry) ChildNames() []string {
	var names []string
	for child := range e.Children() {
		names = append(names, child.Name())
	}
	return names
}

// FindByPath finds a descendant entry by path (e.g., "config/db").
// Returns nil if not found.
func (e *DirEntry) FindByPath(path string) *DirEntry {
	if path == "" || path == "." {
		return e
	}

	parts := strings.Split(path, "/")
	current := e

	for _, part := range parts {
		if part == "" || part == "." {
			continue
		}
		child := current.Child(part)
		if child == nil {
			return nil
		}
		current = child
	}

	return current
}
