// Composer Slice E base-template generator (t-paliad-317).
//
// Produces a minimal Composer-mode .docx whose <w:body> contains the
// 10 default section anchors and whose word/styles.xml declares a
// named style for each stylemap key the composer references. Each
// "preset" (lg-duesseldorf, upc-formal, …) hard-codes the typography
// (font, sizes, colour) so the lawyer can swap between them and see
// the chrome change while the section content carries through
// unchanged (the Q10 base-swap-content-survival contract).
//
// Run:
//
//   go run ./scripts/gen-submission-base -preset lg-duesseldorf -out /tmp/lg-duesseldorf.docx
//   go run ./scripts/gen-submission-base -preset upc-formal     -out /tmp/upc-formal.docx
//
// Both outputs are byte-reproducible (zip mtimes pinned to a fixed
// UTC timestamp so a clean rebuild diff stays at zero bytes).
//
// Cross-firm: the bases this generator emits are firm-agnostic
// (firm = NULL on the catalog row). They contain no HLC branding
// content. Per-firm bases continue to use gen-hl-skeleton-template
// against the proprietary .dotm source.
package main

import (
	"archive/zip"
	"bytes"
	"flag"
	"fmt"
	"os"
	"strings"
	"time"
)

func main() {
	preset := flag.String("preset", "", "preset: lg-duesseldorf | upc-formal")
	out := flag.String("out", "", "output .docx path (required)")
	flag.Parse()

	if *preset == "" || *out == "" {
		fmt.Fprintln(os.Stderr, "usage: gen-submission-base -preset NAME -out PATH")
		os.Exit(2)
	}

	cfg, ok := presets[*preset]
	if !ok {
		fmt.Fprintf(os.Stderr, "unknown preset %q (available: ", *preset)
		first := true
		for k := range presets {
			if !first {
				fmt.Fprint(os.Stderr, ", ")
			}
			fmt.Fprint(os.Stderr, k)
			first = false
		}
		fmt.Fprintln(os.Stderr, ")")
		os.Exit(2)
	}

	docx, err := buildDocx(cfg)
	if err != nil {
		fmt.Fprintln(os.Stderr, "gen-submission-base:", err)
		os.Exit(1)
	}
	if err := os.WriteFile(*out, docx, 0o644); err != nil {
		fmt.Fprintln(os.Stderr, "gen-submission-base: write:", err)
		os.Exit(1)
	}
	fmt.Printf("wrote %s (%d bytes) for preset %s\n", *out, len(docx), *preset)
}

// presetConfig captures everything the generator needs to vary between
// bases: typography defaults (font + size + colour) and the style-name
// prefix that surfaces in the styles.xml.
type presetConfig struct {
	StylePrefix    string // e.g. "LG" / "UPC"
	DefaultFont    string // e.g. "Times New Roman" / "Calibri"
	BodyHalfPoints int    // w:sz value (half-points; 22 = 11pt)
	Heading1Size   int
	Heading2Size   int
	Heading3Size   int
	Heading1Color  string // hex without #
	Heading2Color  string
	Heading3Color  string
	BlockquoteFont string // separate font for the quote style
}

// presets are the seeded base styles for Slice E. Both are intended
// as starting points the firm's admin can refine via the admin editor
// in a later slice — this is the floor, not the ceiling.
var presets = map[string]presetConfig{
	"lg-duesseldorf": {
		StylePrefix:    "LG",
		DefaultFont:    "Times New Roman",
		BodyHalfPoints: 22, // 11pt
		Heading1Size:   28, // 14pt
		Heading2Size:   26, // 13pt
		Heading3Size:   24, // 12pt
		Heading1Color:  "000000",
		Heading2Color:  "000000",
		Heading3Color:  "000000",
		BlockquoteFont: "Times New Roman",
	},
	"upc-formal": {
		StylePrefix:    "UPC",
		DefaultFont:    "Calibri",
		BodyHalfPoints: 22, // 11pt
		Heading1Size:   32, // 16pt
		Heading2Size:   28, // 14pt
		Heading3Size:   24, // 12pt
		Heading1Color:  "1F3864", // UPC dark blue
		Heading2Color:  "1F3864",
		Heading3Color:  "1F3864",
		BlockquoteFont: "Cambria",
	},
}

var fixedTime = time.Date(2026, 5, 26, 0, 0, 0, 0, time.UTC)

func buildDocx(cfg presetConfig) ([]byte, error) {
	var buf bytes.Buffer
	zw := zip.NewWriter(&buf)

	add := func(name, body string) error {
		hdr := &zip.FileHeader{Name: name, Method: zip.Deflate, Modified: fixedTime}
		w, err := zw.CreateHeader(hdr)
		if err != nil {
			return fmt.Errorf("create %s: %w", name, err)
		}
		if _, err := w.Write([]byte(body)); err != nil {
			return fmt.Errorf("write %s: %w", name, err)
		}
		return nil
	}

	if err := add("[Content_Types].xml", contentTypesXML); err != nil {
		return nil, err
	}
	if err := add("_rels/.rels", rootRelsXML); err != nil {
		return nil, err
	}
	if err := add("word/_rels/document.xml.rels", documentRelsXML); err != nil {
		return nil, err
	}
	if err := add("word/styles.xml", buildStylesXML(cfg)); err != nil {
		return nil, err
	}
	if err := add("word/document.xml", buildDocumentXML()); err != nil {
		return nil, err
	}

	if err := zw.Close(); err != nil {
		return nil, fmt.Errorf("finalise zip: %w", err)
	}
	return buf.Bytes(), nil
}

const contentTypesXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
  <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
</Types>`

const rootRelsXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`

// documentRelsXML — empty relationships envelope. The composer's
// hyperlink patch slots fresh <Relationship Type="…/hyperlink"/>
// rows in here at compose time.
const documentRelsXML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>`

// buildStylesXML emits the stylemap-aligned named styles. Each style
// id matches what the catalog row's section_spec.stylemap declares
// for the corresponding key (paragraph / heading_1/2/3 / list_*
// / blockquote / Hyperlink).
//
// "Hyperlink" is the built-in Word style id the composer's MD walker
// emits on link-child runs (Slice D). Including it here makes the
// blue-underline-link rendering land out of the box.
func buildStylesXML(cfg presetConfig) string {
	var b strings.Builder
	b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`)
	b.WriteString(`<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">`)

	// Document defaults — sets the body font + size for every paragraph
	// that doesn't override.
	fmt.Fprintf(&b, `<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="%s" w:hAnsi="%s" w:cs="%s"/><w:sz w:val="%d"/></w:rPr></w:rPrDefault></w:docDefaults>`,
		cfg.DefaultFont, cfg.DefaultFont, cfg.DefaultFont, cfg.BodyHalfPoints)

	// Normal — Word's default paragraph style; nothing fancy.
	b.WriteString(`<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style>`)

	// Body style — body0 alias for the composer's stylemap.paragraph.
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-Body"><w:name w:val="%s body"/><w:basedOn w:val="Normal"/><w:pPr><w:spacing w:after="120" w:line="276" w:lineRule="auto"/></w:pPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix)

	// Headings — three levels with descending sizes + colours.
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-Heading1"><w:name w:val="%s heading 1"/><w:basedOn w:val="Normal"/><w:pPr><w:spacing w:before="320" w:after="160"/></w:pPr><w:rPr><w:b/><w:sz w:val="%d"/><w:color w:val="%s"/></w:rPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix, cfg.Heading1Size, cfg.Heading1Color)
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-Heading2"><w:name w:val="%s heading 2"/><w:basedOn w:val="Normal"/><w:pPr><w:spacing w:before="240" w:after="120"/></w:pPr><w:rPr><w:b/><w:sz w:val="%d"/><w:color w:val="%s"/></w:rPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix, cfg.Heading2Size, cfg.Heading2Color)
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-Heading3"><w:name w:val="%s heading 3"/><w:basedOn w:val="Normal"/><w:pPr><w:spacing w:before="200" w:after="80"/></w:pPr><w:rPr><w:b/><w:sz w:val="%d"/><w:color w:val="%s"/></w:rPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix, cfg.Heading3Size, cfg.Heading3Color)

	// List paragraph styles — same indent as body but with hanging
	// indent so the visible "• " / "N. " prefix from the MD walker
	// aligns cleanly.
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-ListBullet"><w:name w:val="%s list bullet"/><w:basedOn w:val="Normal"/><w:pPr><w:ind w:left="360" w:hanging="360"/><w:spacing w:after="60"/></w:pPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix)
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-ListNumber"><w:name w:val="%s list number"/><w:basedOn w:val="Normal"/><w:pPr><w:ind w:left="360" w:hanging="360"/><w:spacing w:after="60"/></w:pPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix)

	// Blockquote — italic, indented, optional alternative font.
	fmt.Fprintf(&b, `<w:style w:type="paragraph" w:styleId="%s-Quote"><w:name w:val="%s quote"/><w:basedOn w:val="Normal"/><w:pPr><w:ind w:left="720"/><w:spacing w:before="120" w:after="120"/></w:pPr><w:rPr><w:i/><w:rFonts w:ascii="%s" w:hAnsi="%s"/></w:rPr></w:style>`,
		cfg.StylePrefix, cfg.StylePrefix, cfg.BlockquoteFont, cfg.BlockquoteFont)

	// Hyperlink — Word's built-in character-style id matches what the
	// MD walker emits, so the link runs pick up the colour + underline
	// automatically.
	b.WriteString(`<w:style w:type="character" w:styleId="Hyperlink"><w:name w:val="Hyperlink"/><w:rPr><w:color w:val="0563C1"/><w:u w:val="single"/></w:rPr></w:style>`)

	b.WriteString(`</w:styles>`)
	return b.String()
}

// buildDocumentXML emits the composer-mode body — 10 default section
// anchors in the design's §6.1 order, nothing else.
func buildDocumentXML() string {
	var b strings.Builder
	b.WriteString(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`)
	b.WriteString(`<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">`)
	b.WriteString(`<w:body>`)
	for _, key := range []string{
		"letterhead", "caption", "introduction", "requests",
		"facts", "legal_argument", "evidence", "exhibits",
		"closing", "signature",
	} {
		anchor(&b, "{{#section:"+key+"}}")
		anchor(&b, "{{/section:"+key+"}}")
	}
	b.WriteString(`</w:body></w:document>`)
	return b.String()
}

func anchor(b *strings.Builder, text string) {
	b.WriteString(`<w:p><w:r><w:t xml:space="preserve">`)
	b.WriteString(text)
	b.WriteString(`</w:t></w:r></w:p>`)
}
