package lib

import (
	"archive/zip"
	"bytes"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"regexp"
	"strings"

	"github.com/pdfcpu/pdfcpu/pkg/api"
	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
)

// Watermark applies per-request watermarks at serve time.
// Stored files are always clean originals; watermarks are injected on read.
//
// Supported types:
//   - PDF: diagonal text watermark (pdfcpu)
//   - Word (.docx): watermark shape in header
//   - Images: text overlay in bottom-right
//   - Other: pass-through unchanged

// Watermark dispatches to the appropriate watermarking function based on MIME type.
func Watermark(in []byte, mimeType string, label string) ([]byte, error) {
	switch mimeType {
	case "application/pdf":
		return WatermarkPDF(in, label)
	case "image/jpeg", "image/png", "image/gif":
		return WatermarkImage(in, mimeType, label)
	case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
		return WatermarkDOCX(in, label)
	default:
		return in, nil // pass through unsupported types
	}
}

// WatermarkPDF applies a diagonal text watermark to every page of a PDF.
// Uses pdfcpu for PDF manipulation.
// Label format: "CONFIDENTIAL — {UserName} — {Date} — {ProjectName}"
func WatermarkPDF(in []byte, label string) ([]byte, error) {
	if len(in) == 0 {
		return nil, fmt.Errorf("empty input")
	}

	// Build watermark description string for pdfcpu
	// Format: "text, fontname:Helvetica, fontsize:36, color:0.5 0.5 0.5, opacity:0.3, rotation:45, diagonal:1, scale:1 abs"
	wmDesc := fmt.Sprintf("%s, fontname:Helvetica, fontsize:36, color:0.5 0.5 0.5, opacity:0.3, rotation:45, diagonal:1, scale:1 abs",
		escapeWatermarkText(label))

	wm, err := api.TextWatermark(wmDesc, "", true, false, types.POINTS)
	if err != nil {
		return nil, fmt.Errorf("create watermark: %w", err)
	}

	// Apply watermark to all pages
	var out bytes.Buffer
	inReader := bytes.NewReader(in)

	conf := model.NewDefaultConfiguration()
	conf.ValidationMode = model.ValidationRelaxed

	if err := api.AddWatermarks(inReader, &out, nil, wm, conf); err != nil {
		return nil, fmt.Errorf("apply watermark: %w", err)
	}

	return out.Bytes(), nil
}

// escapeWatermarkText escapes special characters for pdfcpu watermark text.
func escapeWatermarkText(text string) string {
	// Escape commas and colons which have special meaning in pdfcpu
	text = strings.ReplaceAll(text, ",", "\\,")
	text = strings.ReplaceAll(text, ":", "\\:")
	return text
}

// WatermarkImage applies a text watermark to an image.
// Supports JPEG, PNG, and GIF (first frame only for GIF).
// The watermark is placed in the bottom-right corner with semi-transparent white text and dark shadow.
func WatermarkImage(in []byte, mimeType string, label string) ([]byte, error) {
	if len(in) == 0 {
		return nil, fmt.Errorf("empty input")
	}

	reader := bytes.NewReader(in)

	// Handle GIF specially (only watermark first frame)
	if mimeType == "image/gif" {
		return watermarkGIF(reader, label)
	}

	// Decode image
	img, format, err := image.Decode(reader)
	if err != nil {
		return nil, fmt.Errorf("decode image: %w", err)
	}

	// Create a new RGBA image to draw on
	bounds := img.Bounds()
	rgba := image.NewRGBA(bounds)
	draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)

	// Draw watermark text
	drawWatermarkText(rgba, label)

	// Encode output
	var out bytes.Buffer
	switch format {
	case "jpeg":
		if err := jpeg.Encode(&out, rgba, &jpeg.Options{Quality: 90}); err != nil {
			return nil, fmt.Errorf("encode jpeg: %w", err)
		}
	case "png":
		if err := png.Encode(&out, rgba); err != nil {
			return nil, fmt.Errorf("encode png: %w", err)
		}
	default:
		// For other formats decoded by image.Decode, output as PNG
		if err := png.Encode(&out, rgba); err != nil {
			return nil, fmt.Errorf("encode png: %w", err)
		}
	}

	return out.Bytes(), nil
}

// watermarkGIF handles GIF watermarking (first frame only).
func watermarkGIF(reader io.Reader, label string) ([]byte, error) {
	g, err := gif.DecodeAll(reader)
	if err != nil {
		return nil, fmt.Errorf("decode gif: %w", err)
	}

	if len(g.Image) == 0 {
		return nil, fmt.Errorf("gif has no frames")
	}

	// Watermark first frame
	firstFrame := g.Image[0]
	bounds := firstFrame.Bounds()
	rgba := image.NewRGBA(bounds)
	draw.Draw(rgba, bounds, firstFrame, bounds.Min, draw.Src)

	drawWatermarkText(rgba, label)

	// Convert back to paletted image
	paletted := image.NewPaletted(bounds, firstFrame.Palette)
	draw.Draw(paletted, bounds, rgba, bounds.Min, draw.Src)
	g.Image[0] = paletted

	var out bytes.Buffer
	if err := gif.EncodeAll(&out, g); err != nil {
		return nil, fmt.Errorf("encode gif: %w", err)
	}

	return out.Bytes(), nil
}

// drawWatermarkText draws watermark text on an RGBA image.
// Uses a simple pixel-based text rendering (8x8 bitmap font style).
// Text is placed in bottom-right corner with shadow effect.
func drawWatermarkText(img *image.RGBA, label string) {
	bounds := img.Bounds()
	width := bounds.Dx()
	height := bounds.Dy()

	// Calculate font scale based on image size
	scale := 1
	if width > 1000 {
		scale = 2
	}
	if width > 2000 {
		scale = 3
	}

	// Measure text
	charWidth := 6 * scale
	charHeight := 10 * scale
	textWidth := len(label) * charWidth
	textHeight := charHeight

	// Position: bottom-right with padding
	padding := 10 * scale
	x := width - textWidth - padding
	y := height - textHeight - padding

	// Clamp to image bounds
	if x < padding {
		x = padding
	}
	if y < padding {
		y = padding
	}

	// Draw shadow (dark, offset by 1-2 pixels)
	shadowColor := color.RGBA{0, 0, 0, 180}
	for i := 1; i <= scale; i++ {
		drawText(img, label, x+i, y+i, charWidth, charHeight, shadowColor, scale)
	}

	// Draw main text (semi-transparent white)
	textColor := color.RGBA{255, 255, 255, 200}
	drawText(img, label, x, y, charWidth, charHeight, textColor, scale)
}

// drawText draws text using a simple bitmap font approach.
// This is a basic implementation that renders readable ASCII text.
func drawText(img *image.RGBA, text string, startX, startY, charWidth, charHeight int, c color.Color, scale int) {
	bounds := img.Bounds()

	for i, ch := range text {
		x := startX + i*charWidth
		if x >= bounds.Max.X {
			break
		}

		// Get character bitmap
		bitmap := getCharBitmap(ch)
		if bitmap == nil {
			continue
		}

		// Draw character
		for row := 0; row < 8; row++ {
			for col := 0; col < 5; col++ {
				if bitmap[row]&(1<<(4-col)) != 0 {
					// Draw scaled pixel
					for sy := 0; sy < scale; sy++ {
						for sx := 0; sx < scale; sx++ {
							px := x + col*scale + sx
							py := startY + row*scale + sy
							if px >= bounds.Min.X && px < bounds.Max.X && py >= bounds.Min.Y && py < bounds.Max.Y {
								img.Set(px, py, c)
							}
						}
					}
				}
			}
		}
	}
}

// getCharBitmap returns an 8-row bitmap for a character (5 bits per row).
// Basic ASCII font supporting uppercase, lowercase, digits, and common punctuation.
func getCharBitmap(ch rune) []byte {
	// 5x8 bitmap font - each byte represents one row, 5 bits used
	fonts := map[rune][]byte{
		'A': {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11, 0x00},
		'B': {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E, 0x00},
		'C': {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E, 0x00},
		'D': {0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E, 0x00},
		'E': {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F, 0x00},
		'F': {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10, 0x00},
		'G': {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0E, 0x00},
		'H': {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11, 0x00},
		'I': {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E, 0x00},
		'J': {0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C, 0x00},
		'K': {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00},
		'L': {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x00},
		'M': {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00},
		'N': {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11, 0x00},
		'O': {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E, 0x00},
		'P': {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10, 0x00},
		'Q': {0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D, 0x00},
		'R': {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11, 0x00},
		'S': {0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E, 0x00},
		'T': {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00},
		'U': {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E, 0x00},
		'V': {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04, 0x00},
		'W': {0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11, 0x00},
		'X': {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11, 0x00},
		'Y': {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x00},
		'Z': {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F, 0x00},
		'a': {0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F, 0x00},
		'b': {0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E, 0x00},
		'c': {0x00, 0x00, 0x0E, 0x11, 0x10, 0x11, 0x0E, 0x00},
		'd': {0x01, 0x01, 0x0F, 0x11, 0x11, 0x11, 0x0F, 0x00},
		'e': {0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E, 0x00},
		'f': {0x06, 0x09, 0x08, 0x1C, 0x08, 0x08, 0x08, 0x00},
		'g': {0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x0E, 0x00},
		'h': {0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x11, 0x00},
		'i': {0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E, 0x00},
		'j': {0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0C, 0x00},
		'k': {0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12, 0x00},
		'l': {0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E, 0x00},
		'm': {0x00, 0x00, 0x1A, 0x15, 0x15, 0x15, 0x15, 0x00},
		'n': {0x00, 0x00, 0x1E, 0x11, 0x11, 0x11, 0x11, 0x00},
		'o': {0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E, 0x00},
		'p': {0x00, 0x00, 0x1E, 0x11, 0x1E, 0x10, 0x10, 0x00},
		'q': {0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x01, 0x00},
		'r': {0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10, 0x00},
		's': {0x00, 0x00, 0x0F, 0x10, 0x0E, 0x01, 0x1E, 0x00},
		't': {0x08, 0x08, 0x1C, 0x08, 0x08, 0x09, 0x06, 0x00},
		'u': {0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0F, 0x00},
		'v': {0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04, 0x00},
		'w': {0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A, 0x00},
		'x': {0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x00},
		'y': {0x00, 0x00, 0x11, 0x11, 0x0F, 0x01, 0x0E, 0x00},
		'z': {0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F, 0x00},
		'0': {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E, 0x00},
		'1': {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E, 0x00},
		'2': {0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F, 0x00},
		'3': {0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E, 0x00},
		'4': {0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02, 0x00},
		'5': {0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E, 0x00},
		'6': {0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E, 0x00},
		'7': {0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00},
		'8': {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E, 0x00},
		'9': {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C, 0x00},
		' ': {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
		'.': {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00},
		',': {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x02, 0x04},
		'-': {0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00},
		'_': {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00},
		':': {0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00, 0x00},
		'/': {0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10, 0x00},
		'@': {0x0E, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0E, 0x00},
		'(': {0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00},
		')': {0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00},
	}

	if bitmap, ok := fonts[ch]; ok {
		return bitmap
	}
	// Return a small square for unknown characters
	return []byte{0x00, 0x1F, 0x11, 0x11, 0x11, 0x1F, 0x00, 0x00}
}

// WatermarkDOCX adds a text watermark to a Word document.
// DOCX is a ZIP file; we unzip, modify word/document.xml to add a watermark shape in the header, and rezip.
func WatermarkDOCX(in []byte, label string) ([]byte, error) {
	if len(in) == 0 {
		return nil, fmt.Errorf("empty input")
	}

	reader := bytes.NewReader(in)
	zipReader, err := zip.NewReader(reader, int64(len(in)))
	if err != nil {
		return nil, fmt.Errorf("open zip: %w", err)
	}

	var out bytes.Buffer
	zipWriter := zip.NewWriter(&out)

	// Track which relationship files we've seen
	var hasHeader1 bool
	headerRelId := "rIdWatermarkHeader"

	// First pass: check if header1.xml exists
	for _, f := range zipReader.File {
		if f.Name == "word/header1.xml" {
			hasHeader1 = true
			break
		}
	}

	for _, f := range zipReader.File {
		rc, err := f.Open()
		if err != nil {
			return nil, fmt.Errorf("open %s: %w", f.Name, err)
		}

		content, err := io.ReadAll(rc)
		rc.Close()
		if err != nil {
			return nil, fmt.Errorf("read %s: %w", f.Name, err)
		}

		// Modify content based on file
		switch f.Name {
		case "word/document.xml":
			// Add header reference to document if not already present
			if !hasHeader1 {
				content = addHeaderReferenceToDocument(content, headerRelId)
			}
		case "word/header1.xml":
			// Add watermark to existing header
			content = addWatermarkToHeader(content, label)
		case "word/_rels/document.xml.rels":
			// Add relationship for header if we created one
			if !hasHeader1 {
				content = addHeaderRelationship(content, headerRelId)
			}
		case "[Content_Types].xml":
			// Ensure header content type is registered
			if !hasHeader1 {
				content = ensureHeaderContentType(content)
			}
		}

		// Write modified content
		w, err := zipWriter.Create(f.Name)
		if err != nil {
			return nil, fmt.Errorf("create %s: %w", f.Name, err)
		}
		if _, err := w.Write(content); err != nil {
			return nil, fmt.Errorf("write %s: %w", f.Name, err)
		}
	}

	// If no header existed, create one with the watermark
	if !hasHeader1 {
		header := createWatermarkHeader(label)
		w, err := zipWriter.Create("word/header1.xml")
		if err != nil {
			return nil, fmt.Errorf("create header: %w", err)
		}
		if _, err := w.Write(header); err != nil {
			return nil, fmt.Errorf("write header: %w", err)
		}
	}

	if err := zipWriter.Close(); err != nil {
		return nil, fmt.Errorf("close zip: %w", err)
	}

	return out.Bytes(), nil
}

// createWatermarkHeader creates a new header XML with a diagonal watermark shape.
func createWatermarkHeader(label string) []byte {
	// VML shape for diagonal watermark text
	// The shape uses a text path to render the watermark diagonally
	header := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
       xmlns:v="urn:schemas-microsoft-com:vml"
       xmlns:o="urn:schemas-microsoft-com:office:office"
       xmlns:w10="urn:schemas-microsoft-com:office:word">
  <w:p>
    <w:pPr>
      <w:pStyle w:val="Header"/>
    </w:pPr>
    <w:r>
      <w:pict>
        <v:shapetype id="_x0000_t136" coordsize="21600,21600" o:spt="136" adj="10800" path="m@7,l@8,m@5,21600l@6,21600e">
          <v:formulas>
            <v:f eqn="sum #0 0 10800"/>
            <v:f eqn="prod #0 2 1"/>
            <v:f eqn="sum 21600 0 @1"/>
            <v:f eqn="sum 0 0 @2"/>
            <v:f eqn="sum 21600 0 @3"/>
            <v:f eqn="if @0 @3 0"/>
            <v:f eqn="if @0 21600 @1"/>
            <v:f eqn="if @0 0 @2"/>
            <v:f eqn="if @0 @4 21600"/>
            <v:f eqn="mid @5 @6"/>
            <v:f eqn="mid @8 @5"/>
            <v:f eqn="mid @7 @8"/>
            <v:f eqn="mid @6 @7"/>
            <v:f eqn="sum @6 0 @5"/>
          </v:formulas>
          <v:path textpathok="t" o:connecttype="custom" o:connectlocs="@9,0;@10,10800;@11,21600;@12,10800" o:connectangles="270,180,90,0"/>
          <v:textpath on="t" fitshape="t"/>
          <v:handles>
            <v:h position="#0,bottomRight" xrange="6629,14971"/>
          </v:handles>
          <o:lock v:ext="edit" text="t" shapetype="t"/>
        </v:shapetype>
        <v:shape id="PowerPlusWaterMarkObject" o:spid="_x0000_s2049" type="#_x0000_t136"
                 style="position:absolute;margin-left:0;margin-top:0;width:527.85pt;height:131.95pt;rotation:315;z-index:-251658752;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin"
                 o:allowincell="f" fillcolor="silver" stroked="f">
          <v:fill opacity=".5"/>
          <v:textpath style="font-family:&quot;Calibri&quot;;font-size:1pt" string="%s"/>
          <w10:wrap anchorx="margin" anchory="margin"/>
        </v:shape>
      </w:pict>
    </w:r>
  </w:p>
</w:hdr>`, escapeXML(label))
	return []byte(header)
}

// addWatermarkToHeader adds a watermark shape to an existing header.
func addWatermarkToHeader(content []byte, label string) []byte {
	// Insert watermark shape before </w:hdr>
	watermarkShape := fmt.Sprintf(`
  <w:p>
    <w:r>
      <w:pict xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w10="urn:schemas-microsoft-com:office:word">
        <v:shapetype id="_x0000_t136" coordsize="21600,21600" o:spt="136" path="m@7,l@8,m@5,21600l@6,21600e">
          <v:textpath on="t" fitshape="t"/>
        </v:shapetype>
        <v:shape type="#_x0000_t136"
                 style="position:absolute;margin-left:0;margin-top:0;width:500pt;height:130pt;rotation:315;z-index:-251658752;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin"
                 fillcolor="silver" stroked="f">
          <v:fill opacity=".5"/>
          <v:textpath style="font-family:Calibri;font-size:1pt" string="%s"/>
          <w10:wrap anchorx="margin" anchory="margin"/>
        </v:shape>
      </w:pict>
    </w:r>
  </w:p>`, escapeXML(label))

	// Find </w:hdr> and insert before it
	str := string(content)
	if idx := strings.LastIndex(str, "</w:hdr>"); idx != -1 {
		str = str[:idx] + watermarkShape + str[idx:]
	}
	return []byte(str)
}

// addHeaderReferenceToDocument adds a header reference to the document.xml.
func addHeaderReferenceToDocument(content []byte, relId string) []byte {
	str := string(content)

	// Find the sectPr element and add headerReference
	// Look for <w:sectPr or create one before </w:body>
	headerRef := fmt.Sprintf(`<w:headerReference w:type="default" r:id="%s"/>`, relId)

	sectPrPattern := regexp.MustCompile(`(<w:sectPr[^>]*>)`)
	if sectPrPattern.MatchString(str) {
		str = sectPrPattern.ReplaceAllString(str, "${1}"+headerRef)
	} else {
		// No sectPr exists, add one before </w:body>
		sectPr := fmt.Sprintf(`<w:sectPr xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">%s</w:sectPr>`, headerRef)
		if idx := strings.LastIndex(str, "</w:body>"); idx != -1 {
			str = str[:idx] + sectPr + str[idx:]
		}
	}

	// Ensure r namespace is declared
	if !strings.Contains(str, `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"`) {
		str = strings.Replace(str, `<w:document `, `<w:document xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" `, 1)
	}

	return []byte(str)
}

// addHeaderRelationship adds the header relationship to document.xml.rels.
func addHeaderRelationship(content []byte, relId string) []byte {
	str := string(content)
	rel := fmt.Sprintf(`<Relationship Id="%s" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/>`, relId)

	// Insert before </Relationships>
	if idx := strings.LastIndex(str, "</Relationships>"); idx != -1 {
		str = str[:idx] + rel + str[idx:]
	}
	return []byte(str)
}

// ensureHeaderContentType ensures the header content type is in [Content_Types].xml.
func ensureHeaderContentType(content []byte) []byte {
	str := string(content)
	if strings.Contains(str, "header1.xml") {
		return content
	}

	override := `<Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>`
	if idx := strings.LastIndex(str, "</Types>"); idx != -1 {
		str = str[:idx] + override + str[idx:]
	}
	return []byte(str)
}

// escapeXML escapes special XML characters.
func escapeXML(s string) string {
	s = strings.ReplaceAll(s, "&", "&amp;")
	s = strings.ReplaceAll(s, "<", "&lt;")
	s = strings.ReplaceAll(s, ">", "&gt;")
	s = strings.ReplaceAll(s, "'", "&apos;")
	s = strings.ReplaceAll(s, "\"", "&quot;")
	return s
}
