package main

import (
	"encoding/json"
	"fmt"
	"io"
	"maps"
	"strconv"
	"strings"
)

func emitExamples(w io.Writer, schemas []Schema, aliases []Alias) error {
	byName := make(map[string]Schema, len(schemas))
	for _, s := range schemas {
		byName[s.Name] = s
	}
	aliasByName := make(map[string]Alias, len(aliases))
	for _, a := range aliases {
		aliasByName[a.Name] = a
	}

	gen := &exampleGen{byName: byName, aliasByName: aliasByName}

	out := make(map[string]any, len(schemas))
	for _, s := range schemas {
		out[s.Name] = gen.forSchema(s, map[string]bool{})
	}

	payload, err := json.MarshalIndent(out, "", "  ")
	if err != nil {
		return err
	}
	if _, err := fmt.Fprintln(w, examplesHeader); err != nil {
		return err
	}
	if _, err := fmt.Fprintf(w, "export const EXAMPLES: Record<string, unknown> = %s;\n", payload); err != nil {
		return err
	}
	return nil
}

type exampleGen struct {
	byName      map[string]Schema
	aliasByName map[string]Alias
}

func (g *exampleGen) forSchema(s Schema, visited map[string]bool) map[string]any {
	obj := make(map[string]any, len(s.Fields))
	for _, f := range s.Fields {
		obj[f.JSONName] = g.forField(f, visited)
	}
	return obj
}

func (g *exampleGen) forField(f Field, visited map[string]bool) any {
	if f.Example != "" {
		return coerceExample(f.Example, baseKind(f.Type))
	}
	if v, ok := firstOneOf(f.Validate); ok {
		return v
	}
	bk := baseKind(f.Type)
	if bk.Kind == KindInt || bk.Kind == KindNumber {
		if v, ok := numericFloor(bk.Kind, f.Validate); ok {
			return v
		}
	}
	return g.forType(f.Type, visited)
}

func (g *exampleGen) forType(t TypeRef, visited map[string]bool) any {
	switch t.Kind {
	case KindString:
		if t.Name == "datetime" {
			return "2025-01-01T00:00:00Z"
		}
		return ""
	case KindInt, KindNumber:
		return 0
	case KindBool:
		return false
	case KindArray:
		if isVisitedRef(*t.Element, visited) {
			return []any{}
		}
		return []any{g.forType(*t.Element, visited)}
	case KindMap:
		return map[string]any{}
	case KindRef:
		if t.Name == "nullable" {
			return nil
		}
		if alias, ok := g.aliasByName[t.Name]; ok {
			return g.forType(alias.Underlying, visited)
		}
		schema, ok := g.byName[t.Name]
		if !ok || visited[t.Name] {
			return map[string]any{}
		}
		next := cloneVisited(visited)
		next[t.Name] = true
		return g.forSchema(schema, next)
	}
	return nil
}

func baseKind(t TypeRef) TypeRef {
	if t.Kind == KindRef && t.Name == "nullable" && t.Inner != nil {
		return *t.Inner
	}
	return t
}

func isVisitedRef(t TypeRef, visited map[string]bool) bool {
	return t.Kind == KindRef && t.Name != "nullable" && visited[t.Name]
}

func cloneVisited(in map[string]bool) map[string]bool {
	out := make(map[string]bool, len(in)+1)
	maps.Copy(out, in)
	return out
}

func numericFloor(kind TypeKind, rules []ValidateRule) (any, bool) {
	for _, r := range rules {
		if (r.Name == "gte" || r.Name == "min") && r.Param != "" {
			return coerceExample(r.Param, TypeRef{Kind: kind}), true
		}
	}
	return nil, false
}

func firstOneOf(rules []ValidateRule) (string, bool) {
	for _, r := range rules {
		if r.Name == "oneof" {
			fields := strings.Fields(r.Param)
			if len(fields) > 0 {
				return fields[0], true
			}
		}
	}
	return "", false
}

func coerceExample(ex string, t TypeRef) any {
	switch t.Kind {
	case KindInt:
		if n, err := strconv.ParseInt(ex, 10, 64); err == nil {
			return n
		}
		return 0
	case KindNumber:
		if n, err := strconv.ParseFloat(ex, 64); err == nil {
			return n
		}
		return 0
	case KindBool:
		return ex == "true"
	case KindString:
		return ex
	default:
		var parsed any
		if err := json.Unmarshal([]byte(ex), &parsed); err == nil {
			return parsed
		}
		return ex
	}
}

const examplesHeader = `// Code generated by tools/openapigen. DO NOT EDIT.`
