package services

// Paliad-side wiring for the pkg/nomen composition engine
// (docs/plans/prd-filename-generator-2026-06-01.md, Slice 1).
//
// pkg/nomen stays pure; this file holds the paliad-specific pieces:
//   - the variable catalogs (which variables each artifact exposes),
//   - the seed system-default Compositions that reproduce the two shipped
//     naming schemes byte-for-byte (#155 draft title, t-paliad-354 .docx
//     filename),
//   - the per-render VarResolvers built from the existing submission_autoname
//     helpers (submissionForumShort / submissionOpponentName / derefString),
//   - and the artifact registry binding artifact -> catalog -> target ->
//     default.
//
// The two public entry points (AutoSubmissionTitle here-adjacent, and
// RenderSubmissionFilename) render through the registry so the engine is the
// single source of truth. Folding the two schemes in as DATA (compositions)
// rather than code is the whole point: future levels (user/firm overrides,
// non-project degradation) layer on without re-deriving the assembly logic.

import (
	"strings"
	"time"

	"mgit.msbls.de/m/paliad/internal/models"
	"mgit.msbls.de/m/paliad/pkg/nomen"
)

// Artifact identifiers. v1 wires the two submission artifacts; further
// artifacts (docforge export, data-zip, projection slug — PRD §4) register
// alongside their own slice, with their own catalog/resolver, when they opt
// in. They are intentionally NOT registered here as placeholders: an
// artifact with no resolver and no consumer would be dead code.
const (
	ArtifactSubmissionDraftTitle   = "submission_draft_title"
	ArtifactSubmissionDocxFilename = "submission_docx_filename"
)

// submissionFilenamePlaceholder fills the bracketed case-number slot when the
// project has no Aktenzeichen yet (t-paliad-354). Kept as a named const so
// the wording stays one-line changeable (m left the exact text open).
const submissionFilenamePlaceholder = "Az. folgt"

// submissionKeywordFallback is the keyword used when neither a user override
// nor a rule name resolves (t-paliad-354).
const submissionKeywordFallback = "submission"

// Artifact binds a named output to its variable catalog, render target, and
// system-default composition. The catalog drives validation + the settings
// palette; the default is the seed used when no override exists.
type Artifact struct {
	ID            string
	Label         string
	LabelEN       string
	Catalog       nomen.VarCatalog
	Target        nomen.RenderTarget
	SystemDefault nomen.Composition
}

// nameArtifacts is the v1 registry. Lookup via NameArtifact.
var nameArtifacts = map[string]Artifact{
	ArtifactSubmissionDraftTitle: {
		ID:            ArtifactSubmissionDraftTitle,
		Label:         "Entwurfstitel",
		LabelEN:       "Draft title",
		Catalog:       submissionTitleCatalog(),
		Target:        nomen.PlainTarget("title"),
		SystemDefault: submissionDraftTitleComposition(),
	},
	ArtifactSubmissionDocxFilename: {
		ID:      ArtifactSubmissionDocxFilename,
		Label:   "Dateiname (.docx)",
		LabelEN: "File name (.docx)",
		Catalog: submissionFilenameCatalog(),
		Target: nomen.FuncTarget{
			NameVal:   "filename",
			Sanitiser: SanitiseSubmissionFileName,
			Suffix:    ".docx",
		},
		SystemDefault: submissionDocxFilenameComposition(),
	},
}

// NameArtifact returns the registered artifact for id, or (zero, false).
func NameArtifact(id string) (Artifact, bool) {
	a, ok := nameArtifacts[id]
	return a, ok
}

// ---------------------------------------------------------------------------
// Seed compositions (the two shipped schemes, as data — PRD §5).
// ---------------------------------------------------------------------------

// submissionDraftTitleComposition reproduces AutoSubmissionTitle (#155):
//
//	<date> <client> ./. <forum> ./. <opponent>
//
// Trailing separators: the date joins the identity block with a space, the
// identity segments join each other with " ./. ". Because separators are
// owned by the left segment, dropping any identity segment (or all of them)
// still yields the byte-exact original — e.g. client-absent renders
// "<date> <forum> ./. <opponent>" with a single space after the date.
func submissionDraftTitleComposition() nomen.Composition {
	return nomen.Composition{
		Version: nomen.Version,
		Segments: []nomen.Segment{
			{Var: "date", Sep: " ", Missing: nomen.Omit()},
			{Var: "client", Sep: " ./. ", Missing: nomen.Omit()},
			{Var: "forum", Sep: " ./. ", Missing: nomen.Omit()},
			{Var: "opponent", Sep: "", Missing: nomen.Omit()},
		},
	}
}

// submissionDocxFilenameComposition reproduces submissionFileName (354):
//
//	<date> <keyword> (<case number>).docx
//
// keyword falls back to a fixed "submission" literal; the case number is
// always rendered in parentheses, falling back to a placeholder when the
// project has no Aktenzeichen. The .docx suffix and per-value sanitisation
// come from the artifact's FuncTarget, not the composition.
func submissionDocxFilenameComposition() nomen.Composition {
	return nomen.Composition{
		Version: nomen.Version,
		Segments: []nomen.Segment{
			{Var: "date", Sep: " ", Missing: nomen.Omit()},
			{Var: "keyword", Sep: " ", Missing: nomen.Literal(submissionKeywordFallback)},
			{Var: "case_number", Sep: "", Wrap: [2]string{"(", ")"}, Missing: nomen.Placeholder(submissionFilenamePlaceholder)},
		},
	}
}

// ---------------------------------------------------------------------------
// Variable catalogs.
// ---------------------------------------------------------------------------

func submissionTitleCatalog() nomen.VarCatalog {
	return nomen.VarCatalog{
		"date":     {Key: "date", Label: "Datum", LabelEN: "Date", Group: "common", Description: "Aktuelles Datum (Europe/Berlin), JJJJ-MM-TT"},
		"client":   {Key: "client", Label: "Mandant", LabelEN: "Client", Group: "parties", Description: "Name des Mandanten (Wurzel der Akte)"},
		"forum":    {Key: "forum", Label: "Forum", LabelEN: "Forum", Group: "proceeding", Description: "Kurzbezeichnung des Forums (UPC, EPA, LG, …)"},
		"opponent": {Key: "opponent", Label: "Gegner", LabelEN: "Opponent", Group: "parties", Description: "Name der Gegenseite"},
	}
}

func submissionFilenameCatalog() nomen.VarCatalog {
	return nomen.VarCatalog{
		"date":        {Key: "date", Label: "Datum", LabelEN: "Date", Group: "common", Description: "Aktuelles Datum (Europe/Berlin), JJJJ-MM-TT"},
		"keyword":     {Key: "keyword", Label: "Stichwort", LabelEN: "Keyword", Group: "document", Description: "Dokument-/Schriftsatztyp; überschreibbar"},
		"case_number": {Key: "case_number", Label: "Aktenzeichen", LabelEN: "Case number", Group: "proceeding", Description: "Aktenzeichen des Verfahrens"},
	}
}

// ---------------------------------------------------------------------------
// Resolvers.
// ---------------------------------------------------------------------------

// nomenDateBerlin formats t as the JJJJ-MM-TT date in Europe/Berlin,
// matching both shipped schemes. A failed zone load leaves t untouched
// (same fallback the original code used).
func nomenDateBerlin(t time.Time) string {
	if loc, err := time.LoadLocation("Europe/Berlin"); err == nil {
		t = t.In(loc)
	}
	return t.Format("2006-01-02")
}

// submissionTitleResolver yields the draft-title variables. now is injected
// (tests pin a fixed instant); the three identity segments resolve from the
// existing helpers and report absence so the composition's Omit rule drops
// them.
func submissionTitleResolver(now time.Time, clientName string, project *models.Project, parties []models.Party, pt *models.ProceedingType) nomen.VarResolver {
	return func(key string) (string, bool) {
		switch key {
		case "date":
			return nomenDateBerlin(now), true
		case "client":
			c := strings.TrimSpace(clientName)
			return c, c != ""
		case "forum":
			f := submissionForumShort(pt)
			return f, f != ""
		case "opponent":
			ourSide := ""
			if project != nil {
				ourSide = derefString(project.OurSide)
			}
			o := submissionOpponentName(parties, ourSide)
			return o, o != ""
		}
		return "", false
	}
}

// submissionFilenameResolver yields the .docx-filename variables. The date is
// render-time "today" (the original used time.Now()); keyword applies the
// override -> lang-aware rule name precedence and reports absence so the
// composition's "submission" literal kicks in; case_number reports absence so
// the "(Az. folgt)" placeholder kicks in.
func submissionFilenameResolver(rule *models.DeadlineRule, project *models.Project, lang, keyword string) nomen.VarResolver {
	return func(key string) (string, bool) {
		switch key {
		case "date":
			return nomenDateBerlin(time.Now()), true
		case "keyword":
			kw := strings.TrimSpace(keyword)
			if kw == "" && rule != nil {
				kw = strings.TrimSpace(rule.Name)
				if strings.EqualFold(lang, "en") && strings.TrimSpace(rule.NameEN) != "" {
					kw = strings.TrimSpace(rule.NameEN)
				}
			}
			return kw, kw != ""
		case "case_number":
			if project != nil && project.CaseNumber != nil {
				c := strings.TrimSpace(*project.CaseNumber)
				if c != "" {
					return c, true
				}
			}
			return "", false
		}
		return "", false
	}
}

// RenderSubmissionFilename produces the user-facing download name for a
// generated submission (t-paliad-354), rendered through the nomen engine:
// "<JJJJ-MM-TT> <keyword> (<case number>).docx". keyword is the user override
// when set, else the lang-aware rule name, else "submission"; the case number
// falls back to "(Az. folgt)" when the project has no Aktenzeichen. Each
// variable value is sanitised for SMB-safe filenames while the frame (spaces,
// parentheses, .docx) is preserved.
func RenderSubmissionFilename(rule *models.DeadlineRule, project *models.Project, lang, keyword string) string {
	art := nameArtifacts[ArtifactSubmissionDocxFilename]
	resolve := submissionFilenameResolver(rule, project, lang, keyword)
	return art.SystemDefault.Render(resolve, art.Target)
}
