package mutator

import (
	"fmt"
	"slices"

	"github.com/databricks/cli/bundle"
	"github.com/databricks/cli/libs/dyn"
)

type jobTaskRewritePattern struct {
	pattern dyn.Pattern
	fn      rewriteFunc
}

func rewritePatterns(base dyn.Pattern) []jobTaskRewritePattern {
	return []jobTaskRewritePattern{
		{
			base.Append(dyn.Key("notebook_task"), dyn.Key("notebook_path")),
			translateNotebookPath,
		},
		{
			base.Append(dyn.Key("spark_python_task"), dyn.Key("python_file")),
			translateFilePath,
		},
		{
			base.Append(dyn.Key("dbt_task"), dyn.Key("project_directory")),
			translateDirectoryPath,
		},
		{
			base.Append(dyn.Key("sql_task"), dyn.Key("file"), dyn.Key("path")),
			translateFilePath,
		},
		{
			base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("whl")),
			translateNoOp,
		},
		{
			base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("jar")),
			translateNoOp,
		},
	}
}

func (m *translatePaths) applyJobTranslations(b *bundle.Bundle, v dyn.Value) (dyn.Value, error) {
	var fallback = make(map[string]string)
	var ignore []string
	var err error

	for key, job := range b.Config.Resources.Jobs {
		dir, err := job.ConfigFileDirectory()
		if err != nil {
			return dyn.InvalidValue, fmt.Errorf("unable to determine directory for job %s: %w", key, err)
		}

		// If we cannot resolve the relative path using the [dyn.Value] location itself,
		// use the job's location as fallback. This is necessary for backwards compatibility.
		fallback[key] = dir

		// Do not translate job task paths if using git source
		if job.GitSource != nil {
			ignore = append(ignore, key)
		}
	}

	// Base pattern to match all tasks in all jobs.
	base := dyn.NewPattern(
		dyn.Key("resources"),
		dyn.Key("jobs"),
		dyn.AnyKey(),
		dyn.Key("tasks"),
		dyn.AnyIndex(),
	)

	// Compile list of patterns and their respective rewrite functions.
	taskPatterns := rewritePatterns(base)
	forEachPatterns := rewritePatterns(base.Append(dyn.Key("for_each_task"), dyn.Key("task")))
	allPatterns := append(taskPatterns, forEachPatterns...)

	for _, t := range allPatterns {
		v, err = dyn.MapByPattern(v, t.pattern, func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
			key := p[2].Key()

			// Skip path translation if the job is using git source.
			if slices.Contains(ignore, key) {
				return v, nil
			}

			dir, err := v.Location().Directory()
			if err != nil {
				return dyn.InvalidValue, fmt.Errorf("unable to determine directory for job %s: %w", key, err)
			}

			return m.rewriteRelativeTo(b, p, v, t.fn, dir, fallback[key])
		})
		if err != nil {
			return dyn.InvalidValue, err
		}
	}

	return v, nil
}