package zddc

import (
	"os"
	"strings"
)

// The .zddc cascade is the authority for canonical-folder behaviour;
// see defaults.zddc.yaml for the conventions and lookups.go for the
// helpers consumers call (DefaultToolAt, AutoOwnAt, VirtualAt,
// IsDeclaredPath, ChildrenDeclaredAt, AvailableToolsAt).

// WriteAutoOwnZddc serialises a creator-grant .zddc into dir, granting
// principalEmail rwcda and recording it in CreatedBy. Used by the file
// API's mkdir post-hook (and by EnsureCanonicalAncestors) to seed
// ownership when a new auto-own folder is materialised.
//
// The grant is identical to what an operator would write by hand —
// direct email pattern, "rwcda" verb set — so the creator can later
// edit the file normally to add collaborators.
//
// Atomic: marshals via the same yaml encoder ParseFile reads
// (round-trip guaranteed) and writes via zddc.WriteFile (which
// performs an atomic temp-write + rename via zddc.WriteAtomic).
func WriteAutoOwnZddc(dir, principalEmail string) error {
	return writeAutoOwn(dir, principalEmail, false)
}

// WriteAutoOwnZddcFenced is the same as WriteAutoOwnZddc but additionally
// sets `acl.inherit: false` — fencing ancestor cascade grants. Used at
// per-user home folders under working/ where the convention is "private
// by default; owner edits the file to add collaborators."
//
// Without the fence, an ancestor `*: r` (e.g. a project-root grant for
// authenticated users) would let any user read every other user's
// working subfolder via cascade — defeating the per-user sandbox.
func WriteAutoOwnZddcFenced(dir, principalEmail string) error {
	return writeAutoOwn(dir, principalEmail, true)
}

func writeAutoOwn(dir, principalEmail string, fenced bool) error {
	rules := ACLRules{
		Permissions: map[string]string{principalEmail: "rwcda"},
	}
	if fenced {
		f := false
		rules.Inherit = &f
	}
	zf := ZddcFile{
		ACL:       rules,
		CreatedBy: principalEmail,
	}
	return WriteFile(dir, zf)
}

// ResolveCanonical returns the on-disk name of the canonical folder
// 'logical' (lowercase) inside parentDir, or "" if no case variant
// exists. Caller decides whether to MkdirAll(parentDir+"/"+logical)
// when "" is returned.
//
// 'logical' is matched case-insensitively against entries returned by
// os.ReadDir(parentDir). The first matching directory entry wins (if
// an operator created both Working/ and working/ on a case-sensitive
// filesystem, the order is filesystem-dependent — that's an unsupported
// state we don't try to recover from).
//
// Returns "" with no error if parentDir doesn't exist or has no match.
func ResolveCanonical(parentDir, logical string) (string, error) {
	entries, err := os.ReadDir(parentDir)
	if err != nil {
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", err
	}
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		if strings.EqualFold(e.Name(), logical) {
			return e.Name(), nil
		}
	}
	return "", nil
}

