package drawio

import (
	"flag"
	"fmt"
	"io"
	"os"
	"strconv"
)

// Интерфейс параметра экспортёра диаграмм
type Option interface {
	apply(opts *Options) // Применить параметр к хранилищу параметров
}

// Ошибка "неподдерживаемый формат"
type ErrUnsupportedFormat struct {
	Format string
}

// Описание ошибки
func (e ErrUnsupportedFormat) Error() string {
	return fmt.Sprintf("unsupported format: '%s'", e.Format)
}

// Проверка эквивалентности ошибок
func (e ErrUnsupportedFormat) Is(target error) bool {
	switch t := target.(type) {
	case ErrUnsupportedFormat:
		return t.Format == e.Format
	}
	return false
}

var _ error = (*ErrUnsupportedFormat)(nil) // Проверка реализации интерфейса

type Format string // Формат экспортированного файла

const (
	PDF  Format = "pdf"
	PNG  Format = "png"
	JPG  Format = "jpg"
	SVG  Format = "svg"
	VSDX Format = "vsdx"
	XML  Format = "xml"
)

// Строковое представление формата
func (f Format) String() string {
	return string(f)
}

// Загрузка формата из строки
func (f *Format) Set(str string) error {
	for _, fmt := range SupportedFormats() {
		if str == string(fmt) {
			*f = Format(str)
			return nil
		}
	}
	return &ErrUnsupportedFormat{str}
}

// Список всех поддерживаемых форматов экспорта
func SupportedFormats() []Format {
	return []Format{"pdf", "png", "jpg", "svg", "vsdx", "xml"}
}

var _ flag.Value = (*Format)(nil) // Поверка реализации интерфейса

// Расширение файла заданного формата
func (f Format) ext() string {
	return "." + string(f)
}

// Параметры экспортёра диаграмм
type Options struct {
	Application      string // Путь к приложению drawio
	EnableXvfb       bool   // Запускать drawio внутри xvfb-run
	Output           string // Путь к папке с экспортированными файлами
	Format           Format // Формат экспортированных файлов
	Recursive        bool   // Рекурсивно сканировать вложенные папки с файлами
	RemovePageSuffix bool   // Удалять суффикс страницы, если это возможно
	Quality          uint   // Качество экспортированного изображения (только для JPEG)
	Transparent      bool   // Прозрачный фона для PNG
	// Включать копию диаграммы в экспортированный файл для PDF, PNG и SVG
	EmbedDiagram   bool
	EmbedSvgImages bool         // Встраивать изображения в файл формата SVG
	Border         uint         // Ширина рамки вокруг диаграмм
	Scale          uint         // Масштаб в процентах размера экспортированных диаграмм
	Width          uint         // Ширина экспортированной диаграммы с сохранением масштаба
	Height         uint         // Высота экспортированной диаграммы с сохранением масштаба
	Crop           bool         // Обрезать результирующий PDF до размера диаграммы
	Uncompressed   bool         // Выводить несжатый XML
	EnablePlugins  bool         // Включить подключаемые модули
	openFile       OpenFileFunc // Открыть файл
	readDir        ReadDirFunc  // Прочитать папку на диске
}

// Функция открытия файла
type OpenFileFunc func(string) (io.ReadCloser, error)

// Функция чтения папки на диске
type ReadDirFunc func(string) ([]os.DirEntry, error)

// Путь к приложению drawio
func (opts Options) App() string {
	if opts.EnableXvfb {
		return "xvfb-run"
	}
	if len(opts.Application) == 0 {
		return "drawio"
	}
	return opts.Application
}

// Путь к папке с экспортированными файлами
func (opts Options) OutDir() string {
	out := opts.Output
	if len(out) == 0 {
		out = "export"
	}
	return out
}

// Расширение экспортированных файлов
func (opts Options) OutExt() string {
	fmt := opts.Format
	if len(fmt) == 0 {
		fmt = Format("pdf")
	}
	return fmt.ext()
}

// Аргументы командной строки drawio
func (opts Options) Args() []string {
	args := []string{}
	if opts.EnableXvfb {
		var app string
		if len(opts.Application) == 0 {
			app = "drawio"
		} else {
			app = opts.Application
		}
		args = append(args, "-a", "-l", app)
	}
	if len(opts.Format) > 0 {
		args = append(args, "--format", string(opts.Format))
	}
	if opts.Quality != 0 {
		args = append(args, "--quality", strconv.Itoa(int(opts.Quality)))
	}
	if opts.Transparent {
		args = append(args, "--transparent")
	}
	if opts.EmbedDiagram {
		args = append(args, "--embed-diagram")
	}
	if opts.EmbedSvgImages {
		args = append(args, "--embed-svg-images")
	}
	if opts.Border != 0 {
		args = append(args, "--border", strconv.Itoa(int(opts.Border)))
	}
	if opts.Scale != 0 {
		args = append(args, "--scale", strconv.Itoa(int(opts.Scale)))
	}
	if opts.Width != 0 {
		args = append(args, "--width", strconv.Itoa(int(opts.Width)))
	}
	if opts.Height != 0 {
		args = append(args, "--height", strconv.Itoa(int(opts.Height)))
	}
	if opts.Crop {
		args = append(args, "--crop")
	}
	if opts.Uncompressed {
		args = append(args, "--uncompressed")
	}
	if opts.EnablePlugins {
		args = append(args, "--enable-plugins")
	}
	return args
}

// Путь к приложению drawio
func WithAppPath(path string) Option {
	return optionApplication(path)
}

type optionApplication string

var _ Option = (*optionApplication)(nil) // Проверка реализации интерфейса

func (opt optionApplication) apply(opts *Options) {
	opts.Application = string(opt)
}

// Запускать drawio внутри xvfb-run
func WithXvfb() Option {
	return optionXvfb{}
}

type optionXvfb struct{}

var _ Option = optionXvfb{}

func (opt optionXvfb) apply(opts *Options) {
	opts.EnableXvfb = true
}

// Путь к папке с экспортированными файлами
func WithOutput(path string) Option {
	return optionOutput(path)
}

type optionOutput string

var _ Option = (*optionOutput)(nil) // Проверка реализации интерфейса

func (opt optionOutput) apply(opts *Options) {
	opts.Output = string(opt)
}

// Формат экспортированных файлов
func WithFormat(format Format) Option {
	return format
}

var _ Option = (*Format)(nil) // Проверка реализации интерфейса

func (opt Format) apply(opts *Options) {
	opts.Format = opt
}

// Рекурсивно сканировать вложенные папки с файлами
func WithRecursive() Option {
	return optionRecursive{}
}

type optionRecursive struct{}

var _ Option = optionRecursive{} // Проверка реализации интерфейса

func (opt optionRecursive) apply(opts *Options) {
	opts.Recursive = true
}

// Удалять суффикс страницы, если это возможно
func WithRemovePageSuffix() Option {
	return optionRemovePageSuffix{}
}

type optionRemovePageSuffix struct{}

var _ Option = optionRemovePageSuffix{} // Проверка реализации интерфейса

func (opt optionRemovePageSuffix) apply(opts *Options) {
	opts.RemovePageSuffix = true
}

// Качество экспортированного изображения (только для JPEG)
func WithQuality(q uint) Option {
	return optionQuality(q)
}

type optionQuality uint

var _ Option = (*optionQuality)(nil) // Проверка реализации интерфейса

func (opt optionQuality) apply(opts *Options) {
	opts.Quality = uint(opt)
}

// Прозрачный фона для PNG
func WithTransparent() Option {
	return optionTransparent{}
}

type optionTransparent struct{}

var _ Option = optionTransparent{} // Проверка реализации интерфейса

func (opt optionTransparent) apply(opts *Options) {
	opts.Transparent = true
}

// Включать копию диаграммы в экспортированный файл для PDF, PNG и SVG
func WithEmbedDiagram() Option {
	return optionEmbedDiagram{}
}

type optionEmbedDiagram struct{}

var _ Option = optionEmbedDiagram{} // Проверка реализации интерфейса

func (opt optionEmbedDiagram) apply(opts *Options) {
	opts.EmbedDiagram = true
}

// Встраивать изображения в файл формата SVG
func WithEmbedSvgImages() Option {
	return optionEmbedSvgImages{}
}

type optionEmbedSvgImages struct{}

var _ Option = optionEmbedSvgImages{} // Проверка реализации интерфейса

func (opt optionEmbedSvgImages) apply(opts *Options) {
	opts.EmbedSvgImages = true
}

// Ширина рамки вокруг диаграмм
func WithBorder(border uint) Option {
	return optionBorder(border)
}

type optionBorder uint

var _ Option = (*optionBorder)(nil) // Проверка реализации интерфейса

func (opt optionBorder) apply(opts *Options) {
	opts.Border = uint(opt)
}

// Масштаб в процентах размера экспортированных диаграмм
func WithScale(scale uint) Option {
	return optionScale(scale)
}

type optionScale uint

var _ Option = (*optionScale)(nil) // Проверка реализации интерфейса

func (opt optionScale) apply(opts *Options) {
	opts.Scale = uint(opt)
}

// Ширина экспортированной диаграммы с сохранением масштаба
func WithWidth(width uint) Option {
	return optionWidth(width)
}

type optionWidth uint

var _ Option = (*optionWidth)(nil) // Проверка реализации интерфейса

func (opt optionWidth) apply(opts *Options) {
	opts.Width = uint(opt)
}

// Высота экспортированной диаграммы с сохранением масштаба
func WithHeight(height uint) Option {
	return optionHeight(height)
}

type optionHeight uint

var _ Option = (*optionHeight)(nil) // Проверка реализации интерфейса

func (opt optionHeight) apply(opts *Options) {
	opts.Height = uint(opt)
}

// Обрезать результирующий PDF до размера диаграммы
func WithCrop() Option {
	return optionCrop{}
}

type optionCrop struct{}

var _ Option = optionCrop{} // Проверка реализации интерфейса

func (opt optionCrop) apply(opts *Options) {
	opts.Crop = true
}

// Выводить несжатый XML
func WithUncompressed() Option {
	return optionUncompressed{}
}

type optionUncompressed struct{}

var _ Option = optionUncompressed{} // Проверка реализации интерфейса

func (opt optionUncompressed) apply(opts *Options) {
	opts.Uncompressed = true
}

// Включить подключаемые модули
func WithEnablePlugins() Option {
	return optionEnablePlugins{}
}

type optionEnablePlugins struct{}

var _ Option = optionEnablePlugins{} // Проверка реализации интерфейса

func (opt optionEnablePlugins) apply(opts *Options) {
	opts.EnablePlugins = true
}

// Открыть файл
func WithOpenFile(openFile OpenFileFunc) Option {
	return optionOpenFile{openFile}
}

type optionOpenFile struct {
	openFile OpenFileFunc
}

var _ Option = optionOpenFile{} // Проверка реализации интерфейса

func (opt optionOpenFile) apply(opts *Options) {
	opts.openFile = opt.openFile
}

// Прочитать папку на диске
func WithReadDir(readDir ReadDirFunc) Option {
	return optionReadDir{readDir}
}

type optionReadDir struct {
	readDir ReadDirFunc
}

var _ Option = optionReadDir{} // Проверка реализации интерфейса

func (opt optionReadDir) apply(opts *Options) {
	opts.readDir = opt.readDir
}

// Пустой параметр
func WithNop() Option {
	return optionNop{}
}

type optionNop struct{}

var _ Option = optionNop{}

func (opt optionNop) apply(opts *Options) {
	// Не требуется действий, так как это пустой параметр
}
