package zddc

import (
	"errors"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

// Project-level helpers for the physical top-level peer layout.
//
// There is no virtual URL space: every record row is addressed at its
// real path (mdl/<party>/<file>.yaml, ssr/<party>.yaml). mdl/ and rsk/
// AGGREGATE across their party subdirs — the peer root renders one
// table of every party's rows (a $party column derived from the real
// subdir name), while <peer>/<party>/ shows that party's rows flat.
// ssr/ aggregates naturally (one flat ssr/<party>.yaml per party) and
// is the authoritative party registry. These helpers back the
// aggregation listing and party-name validation.

// partyNameRE matches a valid party folder / registry-row token —
// starts with [A-Za-z0-9], then any of [A-Za-z0-9.-].
var partyNameRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9.-]*$`)

// ValidPartyName reports whether s is a valid party name. Used by URL
// resolution AND by the SSR create handler to validate user input.
func ValidPartyName(s string) bool {
	return partyNameRE.MatchString(s)
}

// planReviewURLRE matches /<project>/archive/<party>/received/<tracking>/
// — the only URL shape Plan Review accepts. Trailing slash optional.
var planReviewURLRE = regexp.MustCompile(`^/[^/]+/archive/[^/]+/received/[^/]+/?$`)

// IsPlanReviewURL reports whether urlPath is a directory URL eligible
// for the Plan Review composite endpoint — i.e. it points at the
// canonical received/<tracking>/ folder under archive/<party>/.
// Eligibility is purely structural; the handler-side authorisation
// check still gates the actual operation.
func IsPlanReviewURL(urlPath string) bool {
	return planReviewURLRE.MatchString(urlPath)
}

// StripYAMLHTML returns urlPath with a trailing ".html" stripped iff
// the URL has the form-edit shape ".../<name>.yaml.html". Otherwise
// returns urlPath unchanged + false. The form recognizer calls this
// to map a form-edit URL onto the underlying data file.
func StripYAMLHTML(urlPath string) (string, bool) {
	if strings.HasSuffix(urlPath, ".yaml.html") {
		return strings.TrimSuffix(urlPath, ".html"), true
	}
	return urlPath, false
}

// IsSSRCreateURL reports whether urlPath is /<project>/ssr/form.html —
// the SSR "+ Register party" target. Returns the project name when
// matched. The handler writes the new ssr/<party>.yaml registry row.
func IsSSRCreateURL(urlPath string) (string, bool) {
	if urlPath == "" || urlPath[0] != '/' {
		return "", false
	}
	parts := strings.Split(strings.TrimPrefix(urlPath, "/"), "/")
	if len(parts) != 3 || parts[1] != "ssr" || parts[2] != "form.html" {
		return "", false
	}
	project := parts[0]
	if project == "" || strings.HasPrefix(project, ".") || strings.HasPrefix(project, "_") {
		return "", false
	}
	return project, true
}

// IsRollupCreateURL reports whether urlPath is
// /<project>/(mdl|rsk)/form.html — the "+ Add row" target on a
// project-level MDL or RSK aggregate view. Returns the project name +
// peer ("mdl" or "rsk") when matched. The handler reads a `party` field
// from the body and routes the new row into <project>/<peer>/<party>/.
func IsRollupCreateURL(urlPath string) (project, peer string, ok bool) {
	if urlPath == "" || urlPath[0] != '/' {
		return "", "", false
	}
	parts := strings.Split(strings.TrimPrefix(urlPath, "/"), "/")
	if len(parts) != 3 || parts[2] != "form.html" {
		return "", "", false
	}
	if parts[1] != "mdl" && parts[1] != "rsk" {
		return "", "", false
	}
	project = parts[0]
	if project == "" || strings.HasPrefix(project, ".") || strings.HasPrefix(project, "_") {
		return "", "", false
	}
	return project, parts[1], true
}

// ListParties returns the registered party names under <project>/ssr/
// — one per ssr/<party>.yaml file (the authoritative party registry).
// Names are filtered through ValidPartyName. Returns nil + nil when
// ssr/ doesn't exist on disk.
func ListParties(projectAbs string) ([]string, error) {
	reg := filepath.Join(projectAbs, "ssr")
	entries, err := os.ReadDir(reg)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, nil
		}
		return nil, err
	}
	out := make([]string, 0, len(entries))
	for _, e := range entries {
		if e.IsDir() || !strings.HasSuffix(e.Name(), ".yaml") {
			continue
		}
		name := strings.TrimSuffix(e.Name(), ".yaml")
		if !ValidPartyName(name) {
			continue
		}
		out = append(out, name)
	}
	sort.Strings(out)
	return out, nil
}
