package main

import (
	"bufio"
	"encoding/csv"
	"errors"
	"fmt"
	"log"
	"os"
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/storage"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"golang.org/x/text/encoding/charmap"
	"golang.org/x/text/transform"
)

type Row struct {
	Checked   bool
	Sequencia string
	Peso      string
	Produto   string
}

func montaEtq(imp Impressora, seq int, r Row, meta map[string]string) error {

	zpl := "^XA" +
		"^PW609" +
		"^LL500" +

		// Cabeçalho
		CampoTexto(30, 15, 26, 16, "OP: "+meta["OP"]+"   Responsável: "+meta["Responsável"]+"   Lote: "+meta["Número Pesagem"]) +

		LinhaHorizontal(30, 48, 550, 2) +

		// Label destinatário — muito pequeno
		CampoTexto(30, 56, 20, 12, "Material") +

		// Nome — grande
		CampoTexto(30, 80, 50, 30, r.Produto) +

		// Endereço — médio
		CampoTexto(30, 126, 50, 30, r.Peso) +

		LinhaHorizontal(30, 224, 550, 2) +

		// Code 128 do pedido
		CodigoBarras128(80, 234, 100, true, meta["Número Pesagem"]+";"+r.Peso+";"+r.Produto+";"+meta["OP"]) +

		"^XZ"

	return imp.EnviarZPL(zpl)
}

func main() {
	a := app.NewWithID("etiquetas") // required for Windows taskbar icon
	w := a.NewWindow("Imprime Etiquetas")
	w.Resize(fyne.NewSize(800, 600))
	w.SetMaster() // closing this window quits the app on all platforms

	const nomePrinter = "ELGIN L42PRO FULL"

	imp, err := AbrirImpressora(nomePrinter)
	if err != nil {
		log.Fatalf(`Erro ao abrir impressora "%s": %v

Dica: rode no PowerShell para ver os nomes disponíveis:
  Get-Printer | Select-Object Name
`, nomePrinter, err)
	}
	defer imp.Fechar()

	var rows []Row
	var meta map[string]string

	list := widget.NewList(
		func() int { return len(rows) },
		func() fyne.CanvasObject {
			check := widget.NewCheck("", nil)
			seqLabel := widget.NewLabel("")
			pesoLabel := widget.NewLabel("")
			produtoLabel := widget.NewLabel("")
			produtoLabel.Truncation = fyne.TextTruncateEllipsis
			left := container.NewHBox(check, seqLabel, pesoLabel)
			return container.NewBorder(nil, nil, left, nil, produtoLabel)
		},
		func(id widget.ListItemID, obj fyne.CanvasObject) {
			row := rows[id]
			border := obj.(*fyne.Container)
			produtoLabel := border.Objects[0].(*widget.Label)
			left := border.Objects[1].(*fyne.Container)
			check := left.Objects[0].(*widget.Check)
			seqLabel := left.Objects[1].(*widget.Label)
			pesoLabel := left.Objects[2].(*widget.Label)

			check.SetChecked(row.Checked)
			check.OnChanged = func(v bool) { rows[id].Checked = v }
			seqLabel.SetText(row.Sequencia)
			pesoLabel.SetText(row.Peso)
			produtoLabel.SetText(row.Produto)
		},
	)

	status := widget.NewLabel("Nenhum arquivo carregado.")

	openBtn := widget.NewButtonWithIcon("Abrir arquivo", theme.FolderOpenIcon(), func() {
		d := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
			if err != nil || reader == nil {
				return
			}
			defer reader.Close()

			var err2 error
			rows, meta, err2 = processFile(reader)
			if err2 != nil {
				dialog.ShowError(err2, w)
				return
			}
			status.SetText("Carregado: " + reader.URI().Path())
			list.Refresh()
			for k, v := range meta {
				fmt.Printf("%s: %s\n", k, v)
			}
		}, w)
		if cwd, err := os.Getwd(); err == nil {
			if luri, err := storage.ListerForURI(storage.NewFileURI(cwd)); err == nil {
				d.SetLocation(luri)
			}
		}
		d.Show()
	})

	getSelectedBtn := widget.NewButtonWithIcon("Imprimir Seleção", theme.ConfirmIcon(), func() {

		var selected []string

		seq := 0

		// Abrir a impressora

		for _, r := range rows {

			if r.Checked {

				seq++

				// Montar e imprimir a etiqueta com os dados
				etq := montaEtq(*imp, seq, r, meta)

				if etq != nil {
					dialog.ShowError(etq, w)
					return
				}

			}

		}

		if seq == 0 {
			dialog.ShowInformation("Seleção", "Nenhuma linha selecionada.", w)
			return
		}

		dialog.ShowInformation("Linhas Selecionadas", strings.Join(selected, "\n"), w)
	})

	toolbar := container.NewHBox(openBtn, getSelectedBtn)
	content := container.NewBorder(
		container.NewVBox(toolbar, widget.NewSeparator(), status),
		nil, nil, nil,
		list,
	)

	w.SetContent(content)
	w.ShowAndRun()
}

var errInvalidFile = errors.New("Arquivo inválido")

func processFile(reader fyne.URIReadCloser) ([]Row, map[string]string, error) {
	var rows []Row
	meta := make(map[string]string)

	decoded := transform.NewReader(reader, charmap.ISO8859_1.NewDecoder())
	scanner := bufio.NewScanner(decoded)
	firstLine := true
	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == "" {
			continue
		}

		if firstLine {
			firstLine = false
			if !strings.HasPrefix(line, ",,,,") {
				return nil, nil, errInvalidFile
			}
		}

		if !strings.HasPrefix(line, "\"") {
			// Header line: parse as CSV and extract key: value pairs.
			csvPart := strings.SplitN(line, "\t", 2)[0]
			r := csv.NewReader(strings.NewReader(csvPart))
			r.LazyQuotes = true
			fields, err := r.Read()
			if err != nil {
				continue
			}
			for i, f := range fields {
				if !strings.HasSuffix(f, ":") {
					continue
				}
				key := strings.TrimSuffix(f, ":")
				// Find the first non-empty field after this one.
				for j := i + 1; j < len(fields); j++ {
					if v := strings.TrimSpace(fields[j]); v != "" && !strings.HasSuffix(v, ":") {
						meta[key] = v
						break
					}
				}
			}
			continue
		}

		// Data line: outer-quoted CSV field containing inner CSV.
		csvPart := strings.SplitN(line, "\t", 2)[0]
		outerReader := csv.NewReader(strings.NewReader(csvPart))
		outerFields, err := outerReader.Read()
		if err != nil || len(outerFields) == 0 {
			continue
		}

		// The outer field contains inner CSV: ,<seq>,"<peso>",,,<produto>,...
		innerReader := csv.NewReader(strings.NewReader(outerFields[0]))
		innerFields, err := innerReader.Read()
		if err != nil || len(innerFields) < 6 {
			continue
		}

		rows = append(rows, Row{
			Checked:   true,
			Sequencia: strings.TrimSpace(innerFields[1]),
			Peso:      strings.TrimSpace(innerFields[2]),
			Produto:   strings.TrimSpace(innerFields[5]),
		})
	}
	return rows, meta, nil
}
