package services

// Composer wrapper — bridges paliad's submission draft model
// (SubmissionSection + SubmissionBase) to the format-neutral docforge
// .docx composer (pkg/docforge/docx), extracted in slice 2 of the
// docforge train (t-paliad-349 / m/paliad#157).
//
// The full splice/assembly pipeline now lives in pkg/docforge/docx
// (compose.go): macro pre-pass, anchor-pair splicing, append-before-sectPr,
// hyperlink-rels patching, zip repack, and the final placeholder pass. This
// wrapper does the one thing the engine must not know about — mapping
// paliad's DB row types onto the neutral docx.Section / docx.Carrier
// inputs. Behaviour is byte-identical to the pre-extraction composer; the
// in-package compose_test still drives this wrapper end-to-end.
//
// Slice note: the paragraph-level neutral document model (Document / Block
// / Slot) the PRD §3.2 sketches lands in slice 6, where the authoring
// importer and the format exporters actually consume it. Building it now,
// ahead of any consumer, would be speculative and would put the
// byte-identical guarantee at risk for no gain (PRD §4 B3 principle:
// extractions earn their keep this cycle).

import (
	"context"
	"fmt"

	"mgit.msbls.de/m/paliad/pkg/docforge/docx"
)

// SubmissionComposer assembles a base + a draft's sections into a final
// .docx. Stateless; safe for concurrent use.
type SubmissionComposer struct {
	inner *docx.Composer
}

// NewSubmissionComposer wires the composer. The renderer is required — a
// nil renderer is a programmer error and the composer panics at
// construction.
func NewSubmissionComposer(renderer *SubmissionRenderer) *SubmissionComposer {
	return &SubmissionComposer{inner: docx.NewComposer(renderer)}
}

// ComposeOptions carries the per-call composition inputs in paliad's own
// terms (SubmissionSection rows + the SubmissionBase chrome).
type ComposeOptions struct {
	// Sections are the draft's section rows in display order. Included
	// sections render; excluded rows are dropped. The caller is
	// responsible for visibility — by the time the composer runs the rows
	// have already been gated through SubmissionDraftService.Get +
	// can_see_project.
	Sections []SubmissionSection

	// Base supplies the document chrome plus the stylemap for the MD
	// walker. Must not be nil.
	Base *SubmissionBase

	// BaseBytes is the raw .docx bytes for the base, typically fetched
	// from Gitea via the existing template cache.
	BaseBytes []byte

	// Lang ('de' or 'en') selects which content_md_* column the composer
	// reads per section. Defaults to 'de' if empty.
	Lang string

	// Vars is the merged placeholder bag the renderer pass substitutes
	// after assembly.
	Vars PlaceholderMap

	// Missing translates an unbound placeholder key into the marker the
	// lawyer sees in Word.
	Missing MissingPlaceholderFn
}

// Compose runs the full pipeline and returns the merged .docx bytes.
func (c *SubmissionComposer) Compose(ctx context.Context, opts ComposeOptions) ([]byte, error) {
	if opts.Base == nil {
		return nil, fmt.Errorf("submission compose: base required")
	}
	secs := make([]docx.Section, len(opts.Sections))
	for i, s := range opts.Sections {
		secs[i] = docx.Section{
			Key:         s.SectionKey,
			OrderIndex:  s.OrderIndex,
			Included:    s.Included,
			ContentMDDE: s.ContentMDDE,
			ContentMDEN: s.ContentMDEN,
		}
	}
	return c.inner.Compose(ctx, docx.ComposeOptions{
		Sections: secs,
		Carrier: docx.Carrier{
			Bytes:    opts.BaseBytes,
			Stylemap: opts.Base.SectionSpec.Stylemap,
		},
		Lang:    opts.Lang,
		Vars:    opts.Vars,
		Missing: opts.Missing,
	})
}
