package core

import (
	"fmt"
	"net/url"
	"strings"

	"github.com/zk-org/zk/internal/util/paths"
)

// LinkFormatterContext contains metadata used to generate a link.
type LinkFormatterContext struct {
	// Filename of the note
	Filename string `json:"filename"`
	// File path to the note, relative to the notebook root.link-encode-path
	Path string `json:"path"`
	// Absolute file path to the note.
	AbsPath string `json:"absPath" handlebars:"abs-path"`
	// File path to the note, relative to the current directory.
	RelPath string `json:"relPath" handlebars:"rel-path"`
	// Title of the note.
	Title string `json:"title"`
	// Metadata extracted from the YAML frontmatter.
	Metadata map[string]any `json:"metadata"`
}

func NewLinkFormatterContext(path NotebookPath, title string, metadata map[string]any) (LinkFormatterContext, error) {
	relPath, err := path.PathRelToWorkingDir()
	if err != nil {
		return LinkFormatterContext{}, err
	}
	return LinkFormatterContext{
		Filename: path.Filename(),
		Path:     path.Path,
		AbsPath:  path.AbsPath(),
		RelPath:  relPath,
		Title:    title,
		Metadata: metadata,
	}, nil
}

// LinkFormatter formats internal links according to user configuration.
type LinkFormatter func(context LinkFormatterContext) (string, error)

// NewLinkFormatter generates a new LinkFormatter from the user Markdown
// configuration.
func NewLinkFormatter(config MarkdownConfig, templateLoader TemplateLoader) (LinkFormatter, error) {
	switch config.LinkFormat {
	case "markdown", "":
		return NewMarkdownLinkFormatter(config, false)
	case "wiki":
		return NewWikiLinkFormatter(config)
	default:
		return NewCustomLinkFormatter(config, templateLoader)
	}
}

func NewMarkdownLinkFormatter(config MarkdownConfig, onlyHref bool) (LinkFormatter, error) {
	return func(context LinkFormatterContext) (string, error) {
		path := formatPath(context.RelPath, config)
		if !config.LinkEncodePath {
			path = strings.ReplaceAll(path, `\`, `\\`)
			path = strings.ReplaceAll(path, `)`, `\)`)
		}
		if onlyHref {
			return fmt.Sprintf("(%s)", path), nil
		} else {
			title := context.Title
			title = strings.ReplaceAll(title, `\`, `\\`)
			title = strings.ReplaceAll(title, `]`, `\]`)
			return fmt.Sprintf("[%s](%s)", title, path), nil
		}
	}, nil
}

func NewWikiLinkFormatter(config MarkdownConfig) (LinkFormatter, error) {
	return func(context LinkFormatterContext) (string, error) {
		path := formatPath(context.Path, config)
		if !config.LinkEncodePath {
			path = strings.ReplaceAll(path, `\`, `\\`)
			path = strings.ReplaceAll(path, `]]`, `\]]`)
		}
		return "[[" + path + "]]", nil
	}, nil
}

func NewCustomLinkFormatter(config MarkdownConfig, templateLoader TemplateLoader) (LinkFormatter, error) {
	template, err := templateLoader.LoadTemplate(config.LinkFormat)
	if err != nil {
		return nil, fmt.Errorf("failed to render custom link with format: %s: %w", config.LinkFormat, err)
	}

	return func(context LinkFormatterContext) (string, error) {
		context.Filename = formatPath(context.Filename, config)
		context.Path = formatPath(context.Path, config)
		context.RelPath = formatPath(context.RelPath, config)
		context.AbsPath = formatPath(context.AbsPath, config)
		return template.Render(context)
	}, nil
}

func formatPath(path string, config MarkdownConfig) string {
	if config.LinkDropExtension {
		path = paths.DropExt(path)
	}
	if config.LinkEncodePath {
		path = strings.ReplaceAll(url.PathEscape(path), "%2F", "/")
	}
	return path
}
