package jobspec

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// ParseFile reads a jobspec file from disk and dispatches on file
// extension (R-013, REQ-064):
//
//   - .md  → ParseMarkdown (canonical Markdown+frontmatter, R-014/R-015)
//   - .yaml/.yml → ParseMarkdown with the whole file treated as
//     frontmatter and Body = "" (pure YAML, no Markdown body)
//   - .hcl → ParseHCL (legacy adapter; wraps the existing HCL parser
//     and converts Spec{Job, Tasks} into *WorkloadSpec with Kind="Job",
//     REQ-090 migration window)
//
// Unknown extensions return an error. The dispatcher preserves
// `orca job run old-spec.hcl` during the v0.9→v0.10 migration window
// (REQ-090).
func ParseFile(path string) (*WorkloadSpec, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read spec file: %w", err)
	}
	return Dispatch(data, filepath.Base(path))
}

// ParseHCLFile reads an HCL file and parses it via the legacy HCL parser,
// returning the legacy *Spec. It is a convenience wrapper retained for
// tests and direct HCL consumers that need the raw Spec{Job, Tasks}
// shape during the v0.9→v0.10 migration window (REQ-090).
//
// Deprecated: use ParseFile (dispatcher) for new code. HCL is legacy per
// R-013.
func ParseHCLFile(path string) (*Spec, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read spec file: %w", err)
	}
	return ParseHCLLegacy(data, filepath.Base(path))
}

// Dispatch routes raw jobspec bytes on file extension to the
// appropriate parser. filename is used only for HCL (the HCL decoder
// needs a filename for error messages and syntax sniffing).
func Dispatch(data []byte, filename string) (*WorkloadSpec, error) {
	ext := strings.ToLower(filepath.Ext(filename))
	switch ext {
	case ".md":
		return ParseMarkdown(data)
	case ".yaml", ".yml":
		// Pure YAML file: no Markdown body. Treat the whole file as
		// the frontmatter block. Body is empty (R-015: no body to
		// preserve).
		spec, err := parseYAMLFile(data)
		if err != nil {
			return nil, err
		}
		return spec, nil
	case ".hcl":
		return ParseHCL(data, filename)
	default:
		return nil, fmt.Errorf("parse jobspec: unknown extension %q (want .md, .yaml, .yml, or .hcl)", ext)
	}
}

// parseYAMLFile treats the whole file as a frontmatter block (no
// surrounding `---` delimiters, no Markdown body). This routes .yaml
// and .yml files through the same hand-rolled parser as .md.
func parseYAMLFile(data []byte) (*WorkloadSpec, error) {
	block := string(data)
	if strings.TrimSpace(block) == "" {
		return nil, fmt.Errorf("parse yaml: empty file")
	}
	spec, err := parseFrontmatterBlock(block)
	if err != nil {
		return nil, err
	}
	spec.Body = ""
	if err := validateWorkload(spec); err != nil {
		return nil, err
	}
	return spec, nil
}

// ParseHCL parses a legacy HCL jobspec and adapts it into a *WorkloadSpec
// (REQ-064 adapter, REQ-090 migration window). The existing HCL
// Spec{Job, Tasks} shape is converted to:
//
//	Kind:     "Job"
//	Name:     spec.Job.Name
//	Runtime:  {one_of: "process", command: tasks[0].Command}
//
// Body is empty (HCL has no Markdown body). The legacy Spec struct and
// ParseHCLLegacy are retained for direct HCL consumers that have not yet
// migrated.
//
// Deprecated: use the dispatcher (ParseFile/Dispatch). HCL is legacy
// per R-013; the HCL path is retained only for the v0.9→v0.10 migration
// window (REQ-090) and will be removed in v1.0.
func ParseHCL(data []byte, filename string) (*WorkloadSpec, error) {
	spec, err := ParseHCLLegacy(data, filename)
	if err != nil {
		return nil, err
	}
	ws := &WorkloadSpec{
		SpecVersion: "",
		Kind:        "Job",
		Name:        spec.Job.Name,
		Count:       1,
		Body:        "",
	}
	if len(spec.Tasks) > 0 {
		ws.Runtime = &RuntimeBlock{
			OneOf:   "process",
			Command: spec.Tasks[0].Command,
		}
	}
	return ws, nil
}

// ParseHCLLegacy is the original HCL-only parser retained for direct
// HCL consumers (e.g. the cli/job.go toTaskSpecs path during the
// migration window). New code should call ParseHCL (which returns a
// *WorkloadSpec) or the dispatcher. Deprecated: HCL is legacy per
// R-013; see ParseHCL.
func ParseHCLLegacy(data []byte, filename string) (*Spec, error) {
	var spec Spec
	if err := hclDecode(filename, data, &spec); err != nil {
		return nil, fmt.Errorf("decode hcl: %w", err)
	}
	if spec.Job.Name == "" {
		return nil, fmt.Errorf("spec missing job name")
	}
	if len(spec.Tasks) == 0 {
		return nil, fmt.Errorf("spec must have at least one task")
	}
	for i, t := range spec.Tasks {
		if t.Command == "" {
			return nil, fmt.Errorf("task[%d] (%s) missing command", i, t.Name)
		}
	}
	return &spec, nil
}
