package routes

import (
	"bytes"
	"context"
	"database/sql"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"bssapp-backend/db"
	"bssapp-backend/models"
	"bssapp-backend/queries"
	"bssapp-backend/utils"

	"github.com/jung-kurt/gofpdf"
)

// GET /api/pricing/production-product-costing/onml/pdf?n_onml_no=100001
// Generates a PDF export for the costing detail screen (has-cost).
func GetProductionProductCostingOnMLPDFHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/pdf")

	uretimDB := db.GetUretimDB()
	if uretimDB == nil {
		http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
		return
	}

	nOnMLNo := parsePositiveIntOrDefault(r.URL.Query().Get("n_onml_no"), 0)
	if nOnMLNo <= 0 {
		http.Error(w, "n_onml_no zorunlu", http.StatusBadRequest)
		return
	}

	traceID := utils.TraceIDFromRequest(r)
	ctx := utils.ContextWithTraceID(r.Context(), traceID)
	logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.onml.pdf", "n_onml_no", nOnMLNo)
	logger.Info("request start")

	// Header
	hRow, err := queries.GetProductionHasCostDetailHeaderByOnMLNo(ctx, uretimDB, nOnMLNo)
	if err != nil {
		logger.Error("header query prepare error", "err", err)
		http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
		return
	}
	var header models.ProductionHasCostDetailHeader
	if err := hRow.Scan(
		&header.UretimiYapanFirma,
		&header.SonIsEmriVeren,
		&header.FirmaKodu,
		&header.NFirmaID,
		&header.NOnMLNo,
		&header.UrunKodu,
		&header.UrunAdi,
		&header.UretimSekliID,
		&header.UretimSekli,
		&header.MaliyetTarihi,
		&header.DteKayitTarihi,
		&header.SKullaniciAdi,
		&header.LTutarTL,
		&header.LTutarUSD,
		&header.LTutarEURO,
		&header.LTutarGBP,
		&header.SDovizCinsi,
		&header.LTutarDoviz,
		&header.DteGuncellemeTarihi,
		&header.SGuncellemeKullaniciAdi,
		&header.NUrtReceteID,
	); err != nil {
		if err == sql.ErrNoRows {
			http.Error(w, "Kayit bulunamadi", http.StatusNotFound)
			return
		}
		logger.Error("header scan error", "err", err)
		http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
		return
	}

	// Detail groups
	groups, err := loadHasCostDetailGroups(ctx, uretimDB, nOnMLNo)
	if err != nil {
		logger.Error("groups load error", "err", err)
		http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
		return
	}

	// Exchange rates (needed for EUR conversions in PDF summary tables)
	usdRate := 0.0
	eurRate := 0.0
	if mssqlDB := db.GetDB(); mssqlDB != nil {
		row, err := queries.GetProductionHasCostDetailExchangeRatesByDate(ctx, mssqlDB, header.MaliyetTarihi)
		if err == nil {
			var rateDate string
			var gbpRate float64
			_ = row.Scan(&rateDate, &usdRate, &eurRate, &gbpRate)
		}
	}

	pdf := gofpdf.New("L", "mm", "A4", "")
	pdf.SetMargins(8, 8, 8)
	pdf.SetAutoPageBreak(false, 10)
	if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
		http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	export := &costingPDF{
		pdf:     pdf,
		header:  header,
		groups:  groups,
		usdRate: usdRate,
		eurRate: eurRate,
	}
	export.draw()

	if err := pdf.Error(); err != nil {
		http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	var buf bytes.Buffer
	if err := pdf.Output(&buf); err != nil {
		http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	filename := fmt.Sprintf("onml-%d.pdf", nOnMLNo)
	w.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, filename))
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(buf.Bytes())
	logger.Info("request done")
}

func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int) ([]models.ProductionHasCostDetailGroup, error) {
	rows, err := queries.GetProductionHasCostDetailRowsByOnMLNo(ctx, uretimDB, nOnMLNo)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	groups := make([]models.ProductionHasCostDetailGroup, 0, 16)
	groupIndexByName := map[string]int{}

	for rows.Next() {
		var (
			groupName     string
			groupTotal    float64
			groupTotalUSD float64
			nOnMLNoStr    string
			nOnMLDetNoStr string
			hNoStr        string
			mtBolumStr    string
			fiyatGirilen  sql.NullFloat64
			fiyatDoviz    sql.NullString
			maliyeteDahil sql.NullBool
			cmPriceTypeID sql.NullInt64
			item          models.ProductionHasCostDetailGroupItem
		)

		if err := rows.Scan(
			&groupName,
			&groupTotal,
			&groupTotalUSD,
			&nOnMLNoStr,
			&nOnMLDetNoStr,
			&hNoStr,
			&mtBolumStr,
			&item.SKodu,
			&item.SAciklama,
			&item.SRenk,
			&item.SBeden,
			&item.SAciklama2,
			&item.LMiktar,
			&item.LFiyat,
			&item.LTutar,
			&item.SFiyatTipi,
			&item.SDovizCinsi,
			&item.LDovizKuru,
			&item.LDovizFiyati,
			&fiyatGirilen,
			&fiyatDoviz,
			&maliyeteDahil,
			&cmPriceTypeID,
			&item.USDTutar,
			&item.EURTutar,
			&item.GBPTutar,
			&item.SBirim,
			&item.SHammaddeTuruAdi,
			&item.SParcaAdi,
		); err != nil {
			continue
		}

		item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
		item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
		item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
		item.NUrtMTBolumID = strings.TrimSpace(mtBolumStr)
		if fiyatGirilen.Valid {
			item.FiyatGirilen = new(float64)
			*item.FiyatGirilen = fiyatGirilen.Float64
		}
		if fiyatDoviz.Valid {
			item.FiyatDoviz = strings.TrimSpace(fiyatDoviz.String)
		}
		item.MaliyeteDahil = maliyeteDahil.Valid && maliyeteDahil.Bool
		if cmPriceTypeID.Valid {
			v := int(cmPriceTypeID.Int64)
			item.CMPriceTypeID = &v
		}

		idx, ok := groupIndexByName[groupName]
		if !ok {
			groups = append(groups, models.ProductionHasCostDetailGroup{
				SAciklama3:    groupName,
				TotalTutar:    groupTotal,
				TotalUSDTutar: groupTotalUSD,
				Items:         make([]models.ProductionHasCostDetailGroupItem, 0, 24),
			})
			idx = len(groups) - 1
			groupIndexByName[groupName] = idx
		}
		groups[idx].Items = append(groups[idx].Items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return groups, nil
}

// --- PDF drawing ---

type costingPDF struct {
	pdf    *gofpdf.Fpdf
	header models.ProductionHasCostDetailHeader
	groups []models.ProductionHasCostDetailGroup

	usdRate float64
	eurRate float64
}

func (c *costingPDF) draw() {
	c.addPage(true)
	for gi, g := range c.groups {
		c.drawGroup(g, gi == 0)
	}
}

func (c *costingPDF) addPage(fullHeader bool) {
	c.pdf.AddPage()
	if fullHeader {
		c.drawHeaderFull()
	} else {
		c.drawHeaderCompact()
	}
}

func (c *costingPDF) drawHeaderFull() {
	pdf := c.pdf
	pdf.SetFont("dejavu", "B", 14)
	pdf.CellFormat(0, 7, "Maliyet Detay", "", 1, "L", false, 0, "")

	pdf.SetFont("dejavu", "", 9)
	pdf.SetTextColor(60, 60, 60)
	line1 := fmt.Sprintf(
		"OnML No: %s | Maliyet Tarihi: %s | Kayit Tarihi: %s | Uretim Sekli: %s",
		c.header.NOnMLNo,
		formatDateTRDot(c.header.MaliyetTarihi),
		formatDateTRDot(c.header.DteKayitTarihi),
		strings.TrimSpace(c.header.UretimSekli),
	)
	pdf.CellFormat(0, 5, line1, "", 1, "L", false, 0, "")

	line2 := fmt.Sprintf("Urun: %s - %s", strings.TrimSpace(c.header.UrunKodu), strings.TrimSpace(c.header.UrunAdi))
	pdf.CellFormat(0, 5, line2, "", 1, "L", false, 0, "")

	firmaLabel := strings.TrimSpace(c.header.FirmaKodu)
	if strings.TrimSpace(c.header.UretimiYapanFirma) != "" {
		firmaLabel = fmt.Sprintf("%s - %s", firmaLabel, strings.TrimSpace(c.header.UretimiYapanFirma))
	}
	line3 := fmt.Sprintf(
		"Firma: %s | 2.Firma: %s | Kaydeden: %s | Son Guncelleme: %s (%s)",
		firmaLabel,
		strings.TrimSpace(c.header.SonIsEmriVeren),
		strings.TrimSpace(c.header.SKullaniciAdi),
		formatDateTRDot(c.header.DteGuncellemeTarihi),
		strings.TrimSpace(c.header.SGuncellemeKullaniciAdi),
	)
	pdf.CellFormat(0, 5, line3, "", 1, "L", false, 0, "")

	pdf.SetTextColor(0, 0, 0)
	pdf.Ln(2)

	// Header summary tables (stacked)
	c.drawHeaderSummaryTables()
}

func (c *costingPDF) drawHeaderCompact() {
	pdf := c.pdf
	pdf.SetFont("dejavu", "B", 10.5)
	firmaLabel := strings.TrimSpace(c.header.FirmaKodu)
	if strings.TrimSpace(c.header.UretimiYapanFirma) != "" {
		firmaLabel = fmt.Sprintf("%s - %s", firmaLabel, strings.TrimSpace(c.header.UretimiYapanFirma))
	}
	title := fmt.Sprintf(
		"OnML %s | %s - %s | %s | %s",
		c.header.NOnMLNo,
		strings.TrimSpace(c.header.UrunKodu),
		strings.TrimSpace(c.header.UrunAdi),
		formatDateTRDot(c.header.MaliyetTarihi),
		firmaLabel,
	)
	pdf.CellFormat(0, 6, title, "", 1, "L", false, 0, "")
	pdf.Ln(1)
}

type pdfPartSummaryRow struct {
	name string
	try  float64
	usd  float64
	eur  float64
}

type pdfGroupTotalRow struct {
	group string
	try   float64
	usd   float64
	eur   float64
}

func (c *costingPDF) drawHeaderSummaryTables() {
	pdf := c.pdf

	groupRows, grandTRY, grandUSD, grandEUR := c.computeGroupTotals()

	// Table styling (use same brand palette as statements PDF)
	// colorPrimary/colorSecondary/colorDetailFill are in statements_pdf.go (same package).
	pdf.SetFont("dejavu", "B", 9.0)
	pdf.SetTextColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
	pdf.CellFormat(0, 5.5, "Ozet", "", 1, "L", false, 0, "")
	pdf.SetTextColor(0, 0, 0)

	// Group totals table
	pdf.SetFont("dejavu", "B", 8.2)
	pdf.CellFormat(0, 4.8, "Grup Toplamlari", "", 1, "L", false, 0, "")
	gCols := []string{"Grup", "TRY", "USD", "EUR"}
	gW := []float64{30, 22, 22, 22}
	totalRows := append(groupRows, pdfGroupTotalRow{group: "TOPLAM", try: grandTRY, usd: grandUSD, eur: grandEUR})
	c.drawMiniTable(gCols, gW, func(i int) []string {
		if i >= len(totalRows) {
			return nil
		}
		r := totalRows[i]
		return []string{r.group, pdfMoney(r.try), pdfMoney(r.usd), pdfMoney(r.eur)}
	}, len(totalRows), true, true)
	pdf.Ln(2)
}

func (c *costingPDF) computePartSummary() []pdfPartSummaryRow {
	byName := map[string]*pdfPartSummaryRow{}
	for _, g := range c.groups {
		for _, it := range g.Items {
			name := strings.TrimSpace(it.SParcaAdi)
			if name == "" {
				name = "-"
			}
			row := byName[name]
			if row == nil {
				row = &pdfPartSummaryRow{name: name}
				byName[name] = row
			}
			row.try += it.LTutar
			row.usd += it.USDTutar
			// EUR isn't directly returned by the has-cost query; derive from USD using exchange rates when available.
			if c.usdRate > 0 && c.eurRate > 0 {
				row.eur += it.USDTutar * (c.usdRate / c.eurRate)
			}
		}
	}
	out := make([]pdfPartSummaryRow, 0, len(byName))
	for _, v := range byName {
		out = append(out, *v)
	}
	// stable order
	sort.Slice(out, func(i, j int) bool { return out[i].name < out[j].name })
	return out
}

func (c *costingPDF) computeGroupTotals() (rows []pdfGroupTotalRow, grandTRY float64, grandUSD float64, grandEUR float64) {
	want := []string{"CM2", "FABRIC", "DT", "TP"}
	by := map[string]*pdfGroupTotalRow{}
	for _, w := range want {
		by[w] = &pdfGroupTotalRow{group: w}
	}

	for _, g := range c.groups {
		grp := strings.ToUpper(strings.TrimSpace(g.SAciklama3))
		t := by[grp]
		if t == nil {
			continue
		}
		// g.TotalTutar is TRY total; g.TotalUSDTutar is USD total
		t.try += g.TotalTutar
		t.usd += g.TotalUSDTutar
		if c.usdRate > 0 && c.eurRate > 0 {
			t.eur += g.TotalUSDTutar * (c.usdRate / c.eurRate)
		}
	}

	for _, w := range want {
		r := by[w]
		rows = append(rows, *r)
		grandTRY += r.try
		grandUSD += r.usd
		grandEUR += r.eur
	}
	return rows, grandTRY, grandUSD, grandEUR
}

func (c *costingPDF) drawMiniTable(cols []string, widths []float64, rowFn func(i int) []string, rowCount int, zebra bool, emphasizeLastRow bool) {
	pdf := c.pdf

	// Header row
	pdf.SetFont("dejavu", "B", 7.8)
	pdf.SetFillColor(colorSecondary[0], colorSecondary[1], colorSecondary[2])
	pdf.SetDrawColor(160, 140, 100)
	pdf.SetTextColor(0, 0, 0)

	x0 := pdf.GetX()
	y0 := pdf.GetY()
	x := x0
	hH := 5.4
	for i, col := range cols {
		pdf.Rect(x, y0, widths[i], hH, "DF")
		pdf.SetXY(x+0.8, y0+1.2)
		pdf.CellFormat(widths[i]-1.6, hH-2.4, col, "", 0, "C", false, 0, "")
		x += widths[i]
	}
	pdf.SetXY(x0, y0+hH)

	// Data rows
	pdf.SetDrawColor(220, 220, 220)
	for i := 0; i < rowCount; i++ {
		row := rowFn(i)
		if row == nil {
			break
		}
		isLast := emphasizeLastRow && (i == rowCount-1)
		fill := zebra && (i%2 == 1)

		// Style rules:
		// - Total row: primary fill + bigger bold text
		// - Zebra rows: detail fill
		// - Normal: white
		if isLast {
			pdf.SetFont("dejavu", "B", 8.6)
			pdf.SetTextColor(255, 255, 255)
			pdf.SetFillColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
		} else {
			pdf.SetFont("dejavu", "", 7.4)
			pdf.SetTextColor(30, 30, 30)
			if fill {
				pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2])
			} else {
				pdf.SetFillColor(255, 255, 255)
			}
		}
		x = x0
		y := pdf.GetY()
		rh := 5.0
		if isLast {
			rh = 5.6
		}
		for cidx, val := range row {
			style := ""
			if fill || isLast {
				style = "DF"
			}
			pdf.Rect(x, y, widths[cidx], rh, style)
			align := "L"
			if cidx > 0 {
				align = "R"
			}
			pdf.SetXY(x+0.8, y+(rh-3.5)/2)
			pdf.CellFormat(widths[cidx]-1.6, 3.5, val, "", 0, align, false, 0, "")
			x += widths[cidx]
		}
		pdf.SetXY(x0, y+rh)
	}
	pdf.SetTextColor(0, 0, 0)
	pdf.SetFont("dejavu", "", 7.4)
}

func formatDateTRDot(s string) string {
	s = strings.TrimSpace(s)
	if s == "" {
		return "-"
	}
	layouts := []string{
		"2006-01-02 15:04:05",
		"2006-01-02 15:04",
		"2006-01-02",
	}
	for _, l := range layouts {
		if t, err := time.ParseInLocation(l, s, time.Local); err == nil {
			return t.Format("02.01.2006")
		}
	}
	return s
}

func (c *costingPDF) drawGroup(g models.ProductionHasCostDetailGroup, firstGroup bool) {
	pdf := c.pdf
	// Reset any font/color left over from header summary tables.
	pdf.SetFont("dejavu", "", 7.2)
	pdf.SetTextColor(0, 0, 0)

	// Group bar
	c.drawGroupBar(g, false)

	// Columns
	// Keep total width <= A4 landscape printable width (297 - left/right margins).
	// Also force USD/TRY unit+total columns to always be visible.
	cols := []string{
		"No",
		"Parca",
		"Hammadde",
		"Kod",
		"Aciklama",
		"Renk",
		"Miktar",
		"Br",
		"Br\nFiyat",
		"Pr\nBr",
		"USD\nFiyat",
		"USD\nTutar",
		"TRY\nFiyat",
		"TRY\nTutar",
	}
	wn := []float64{8, 20, 22, 30, 56, 14, 14, 10, 16, 12, 16, 16, 16, 16} // sum = 252

	c.drawTableHeader(cols, wn)
	// PDF-specific ordering: by hammadde turu no, then code.
	items := append([]models.ProductionHasCostDetailGroupItem(nil), g.Items...)
	sort.Slice(items, func(i, j int) bool {
		ai, _ := strconv.Atoi(strings.TrimSpace(items[i].NHammaddeTuruNo))
		aj, _ := strconv.Atoi(strings.TrimSpace(items[j].NHammaddeTuruNo))
		if ai != aj {
			return ai < aj
		}
		return strings.TrimSpace(items[i].SKodu) < strings.TrimSpace(items[j].SKodu)
	})
	for i, it := range items {
		c.drawRowWithGroup(it, wn, cols, g, i)
	}
	pdf.Ln(2)
	_ = firstGroup
}

func (c *costingPDF) drawGroupBar(g models.ProductionHasCostDetailGroup, continued bool) {
	pdf := c.pdf
	pdf.SetFont("dejavu", "B", 10)
	pdf.SetFillColor(colorSecondary[0], colorSecondary[1], colorSecondary[2])
	pdf.SetTextColor(0, 0, 0)
	name := strings.TrimSpace(g.SAciklama3)
	if continued {
		name = name + " (devam)"
	}
	pdf.CellFormat(0, 6, fmt.Sprintf("%s | Toplam TRY: %s | Toplam USD: %s", name, pdfMoney(g.TotalTutar), pdfMoney(g.TotalUSDTutar)), "1", 1, "L", true, 0, "")
	pdf.SetTextColor(0, 0, 0)
}

func (c *costingPDF) drawTableHeader(cols []string, wn []float64) {
	pdf := c.pdf
	pdf.SetFont("dejavu", "B", 8)
	pdf.SetFillColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
	pdf.SetDrawColor(120, 90, 20)
	pdf.SetTextColor(255, 255, 255)

	// Compute a stable header height based on wrapped labels.
	maxLines := 1
	for i, col := range cols {
		lines := pdf.SplitLines([]byte(col), wn[i]-1.6)
		if len(lines) > maxLines {
			maxLines = len(lines)
		}
	}
	lineH := 3.5
	headerH := float64(maxLines)*lineH + 1.2
	if headerH < 7.0 {
		headerH = 7.0
	}

	x0 := pdf.GetX()
	y0 := pdf.GetY()
	x := x0
	for i, col := range cols {
		c.drawHeaderCellWrap(x, y0, wn[i], headerH, col)
		x += wn[i]
	}
	pdf.SetXY(x0, y0+headerH)
	pdf.SetTextColor(0, 0, 0)
	pdf.SetDrawColor(220, 220, 220)
}

func (c *costingPDF) ensureSpace(h float64) (newPage bool) {
	pdf := c.pdf
	_, pageH := pdf.GetPageSize()
	_, _, _, mb := pdf.GetMargins()
	if pdf.GetY()+h > pageH-mb {
		c.addPage(false)
		return true
	}
	return false
}

func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem, wn []float64, cols []string, g models.ProductionHasCostDetailGroup, rowIndex int) {
	pdf := c.pdf
	pdf.SetFont("dejavu", "", 7.2)
	pdf.SetDrawColor(220, 220, 220)

	// Compute row height based on key wrapped cells.
	parcaLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SParcaAdi)), wn[1]-2)
	hLabel := strings.TrimSpace(it.NHammaddeTuruNo)
	if strings.TrimSpace(it.SHammaddeTuruAdi) != "" {
		hLabel = hLabel + " " + strings.TrimSpace(it.SHammaddeTuruAdi)
	}
	hLines := pdf.SplitLines([]byte(hLabel), wn[2]-2)
	kodLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SKodu)), wn[3]-2)
	descLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SAciklama)), wn[4]-2)

	maxLines := len(descLines)
	if len(parcaLines) > maxLines {
		maxLines = len(parcaLines)
	}
	if len(hLines) > maxLines {
		maxLines = len(hLines)
	}
	if len(kodLines) > maxLines {
		maxLines = len(kodLines)
	}

	rowH := float64(maxLines) * 3.5
	if rowH < 5.0 {
		rowH = 5.0
	}
	if c.ensureSpace(rowH + 12.0) {
		// Redraw group bar + table header on new page.
		c.drawGroupBar(g, true)
		c.drawTableHeader(cols, wn)
	}

	x0 := pdf.GetX()
	y0 := pdf.GetY()

	fill := rowIndex%2 == 1
	if fill {
		pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2])
	} else {
		pdf.SetFillColor(255, 255, 255)
	}

	c.drawCell(x0, y0, wn[0], rowH, it.NOnMLDetNo, "R", fill)
	x := x0 + wn[0]
	c.drawCellWrap(x, y0, wn[1], rowH, strings.TrimSpace(it.SParcaAdi), "L", fill)
	x += wn[1]
	c.drawCellWrap(x, y0, wn[2], rowH, hLabel, "L", fill)
	x += wn[2]
	c.drawCellWrap(x, y0, wn[3], rowH, strings.TrimSpace(it.SKodu), "L", fill)
	x += wn[3]
	c.drawCellWrap(x, y0, wn[4], rowH, strings.TrimSpace(it.SAciklama), "L", fill)
	x += wn[4]
	c.drawCell(x, y0, wn[5], rowH, strings.TrimSpace(it.SRenk), "L", fill)
	x += wn[5]
	c.drawCell(x, y0, wn[6], rowH, pdfQty(it.LMiktar), "R", fill)
	x += wn[6]
	c.drawCell(x, y0, wn[7], rowH, strings.TrimSpace(it.SBirim), "C", fill)
	x += wn[7]

	// Prefer input price if present; otherwise lFiyat.
	price := it.LFiyat
	cur := strings.TrimSpace(it.SDovizCinsi)
	if it.FiyatGirilen != nil && *it.FiyatGirilen > 0 {
		price = *it.FiyatGirilen
		if strings.TrimSpace(it.FiyatDoviz) != "" {
			cur = strings.TrimSpace(it.FiyatDoviz)
		}
	}
	c.drawCell(x, y0, wn[8], rowH, pdfMoney(price), "R", fill)
	x += wn[8]
	c.drawCell(x, y0, wn[9], rowH, cur, "C", fill)
	x += wn[9]

	// Always show USD/TRY unit+total.
	// In URETIM schema: lFiyat/lTutar are in TRY, lDovizFiyati/usdTutar are in USD.
	c.drawCell(x, y0, wn[10], rowH, pdfMoney(it.LDovizFiyati), "R", fill)
	x += wn[10]
	usdTotal := it.USDTutar
	if usdTotal == 0 && it.LMiktar != 0 && it.LDovizFiyati != 0 {
		usdTotal = it.LMiktar * it.LDovizFiyati
	}
	c.drawCell(x, y0, wn[11], rowH, pdfMoney(usdTotal), "R", fill)
	x += wn[11]

	// Prefer input price if present; otherwise lFiyat.
	unitTRY := it.LFiyat
	if it.FiyatGirilen != nil && *it.FiyatGirilen > 0 && strings.EqualFold(strings.TrimSpace(it.FiyatDoviz), "TRY") {
		unitTRY = *it.FiyatGirilen
	}
	c.drawCell(x, y0, wn[12], rowH, pdfMoney(unitTRY), "R", fill)
	x += wn[12]
	c.drawCell(x, y0, wn[13], rowH, pdfMoney(it.LTutar), "R", fill)

	pdf.SetXY(x0, y0+rowH)
}

func (c *costingPDF) drawHeaderCellWrap(x, y, w, h float64, txt string) {
	pdf := c.pdf
	pdf.Rect(x, y, w, h, "DF")
	pdf.SetXY(x+0.8, y+0.6)
	pdf.MultiCell(w-1.6, 3.5, txt, "", "C", true)
	// restore cursor (MultiCell moves Y)
	pdf.SetXY(x+w, y)
}

func (c *costingPDF) drawCell(x, y, w, h float64, txt, align string, fill bool) {
	pdf := c.pdf
	style := ""
	if fill {
		style = "DF"
	}
	pdf.Rect(x, y, w, h, style)
	pdf.SetXY(x+0.8, y+(h-3.5)/2)
	pdf.CellFormat(w-1.6, 3.5, txt, "", 0, align, false, 0, "")
}

func (c *costingPDF) drawCellWrap(x, y, w, h float64, txt, align string, fill bool) {
	pdf := c.pdf
	style := ""
	if fill {
		style = "DF"
	}
	pdf.Rect(x, y, w, h, style)
	pdf.SetXY(x+0.8, y+0.6)
	pdf.MultiCell(w-1.6, 3.5, txt, "", align, false)
	// restore cursor (MultiCell moves Y)
	pdf.SetXY(x+w, y)
}

func pdfMoney(v float64) string {
	// 2 decimals, dot
	return fmt.Sprintf("%.2f", v)
}

func pdfQty(v float64) string {
	// 4 decimals (screen-like)
	return fmt.Sprintf("%.4f", v)
}
