package listing

import "time"

// FileInfo matches Caddy's browse JSON output exactly (with one ZDDC-
// specific extension: Virtual). The archive browser (source.js) expects
// this exact shape.
type FileInfo struct {
	Name      string    `json:"name"` // filename; directories have a trailing "/"
	Size      int64     `json:"size"`
	URL       string    `json:"url"` // relative URL to the item
	ModTime   time.Time `json:"mod_time"`
	Mode      uint32    `json:"mode"`
	IsDir     bool      `json:"is_dir"`
	IsSymlink bool      `json:"is_symlink"` // always false — no real symlinks served

	// Virtual marks an entry that doesn't exist on disk yet but is
	// surfaced in listings as a synthetic affordance — e.g. the per-user
	// <viewer-email>/ entry under working/. A first write to a virtual
	// path materialises a real folder (with auto-own .zddc); subsequent
	// listings drop the synthetic entry. Clients can use this flag to
	// render the entry differently (placeholder badge, drop-target hint).
	Virtual bool `json:"virtual,omitempty"`

	// DisplayName is the human-friendly label rendered by clients in
	// place of Name when set. Sourced from the directory's .zddc
	// `display:` map (matched case-insensitively on the on-disk
	// basename). Empty = render Name. Never empty for an explicit
	// override — clients shouldn't infer a default from absence here.
	DisplayName string `json:"display_name,omitempty"`

	// Declared marks this entry as one the .zddc cascade names via
	// the parent's Paths: map — i.e. it's a "known" child of this
	// directory, with rules already defined. Used by clients (e.g.
	// shared/nav.js's stage strip) to filter the project root's
	// listing down to the canonical stages without hardcoding the
	// names. Real on-disk dirs and virtual placeholders both get
	// Declared=true when their name matches the cascade.
	Declared bool `json:"declared,omitempty"`

	// Title is the human-readable project / folder name pulled from
	// the entry's own `.zddc` file (its `title:` field), surfaced
	// directly in the directory listing so a generic client can
	// render a friendly label without an extra round-trip per entry.
	// Most useful at the deployment root, where it gives the landing
	// page project names without a separate API. Empty when the
	// entry has no `.zddc` or its `.zddc` doesn't set `title:`.
	Title string `json:"title,omitempty"`

	// Writable surfaces whether the calling principal has write
	// authority on this entry — answered by running the policy decider
	// with ActionWrite on the entry's path, with the same admin-bypass
	// branch that the file API uses. Clients (notably the in-place
	// markdown editor) read this to mount in read-only mode when a
	// PUT would 403, instead of letting the user type then lose their
	// changes on save.
	//
	// omitempty: the listing JSON omits the field when false so the
	// wire payload doesn't bloat; clients should treat absence as
	// false-or-unknown and gate writes accordingly. Read-only-by-
	// default is the safer client-side fallback if the server forgets
	// to populate it.
	//
	// Superseded by Verbs (which carries the full verb set); kept
	// alongside it for the transition window so existing clients
	// reading writable: don't break. New clients should read Verbs and
	// check for 'w'.
	Writable bool `json:"writable,omitempty"`

	// Verbs is the canonical "rwcda" subset granted to the calling
	// principal at this entry's URL. Computed by running the policy
	// decider for each of the five actions and unioning the allowed
	// bits, with the same active-admin bypass that Writable uses.
	//
	// Semantics per entry kind:
	//   - file: verbs from the parent directory's chain (files have no
	//     .zddc of their own; they inherit). Same chain Writable uses.
	//   - directory: verbs from the subdirectory's OWN chain, so a
	//     fenced/extended .zddc inside it shows through. The client
	//     interprets per-kind: `w` on a dir = "I can rename this
	//     folder", `c` on a dir = N/A at this URL (use path-scoped
	//     /.profile/access?path= for "can I create inside this dir").
	//
	// omitempty: empty set ("") is the explicit-deny case; absence
	// means the server didn't populate it. Clients should treat both
	// as "no permissions known" and fall back to a server round-trip
	// (or just disable affordances) rather than assuming any grant.
	Verbs string `json:"verbs,omitempty"`

	// History is true when this entry is a text file in a history: true
	// cascade subtree — i.e. every save is versioned into the sibling
	// .history/ store and GET <url>?history=1 lists prior versions. The
	// browse client uses it to show "History…" / diff affordances only
	// where they apply. False/absent for directories, virtual entries,
	// and files outside a history-enabled subtree.
	History bool `json:"history,omitempty"`
}
