package semanticreconcile

import (
	"errors"
	"fmt"
	"io/fs"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs"
)

const (
	PromptID      = "generic.semantic_reconciliation"
	PromptVersion = "v1"
	promptRoot    = "assets/prompts"
)

var promptFiles = []promptfs.ModulePromptFile{
	{Name: "prompt.yaml", Path: "prompts/prompt.yaml"},
	{Name: "system.md", Path: "prompts/system.md"},
	{Name: "protocol.md", Path: "prompts/protocol.md"},
	{Name: "instructions.md", Path: "prompts/instructions.md"},
	{Name: "candidates.md", Path: "prompts/candidates.md"},
	{Name: "transcript-windows.md", Path: "prompts/transcript-windows.md"},
}

// RegisterAssets registers the generic reconciliation prompt and response
// schema as one production-owned asset set.
func RegisterAssets(registry *llm.AssetRegistry) error {
	if registry == nil {
		return fmt.Errorf("semantic reconciliation asset registry must not be nil")
	}
	if err := ensureAssetsAbsent(registry); err != nil {
		return err
	}

	assets, err := assetFS()
	if err != nil {
		return err
	}
	prompts, err := promptfs.ModulePromptFS(PromptID, assets, append([]promptfs.ModulePromptFile(nil), promptFiles...))
	if err != nil {
		return fmt.Errorf("prepare semantic reconciliation prompt assets: %w", err)
	}
	if err := registry.RegisterPromptFS(prompts, promptRoot); err != nil {
		return fmt.Errorf("register semantic reconciliation prompt assets: %w", err)
	}
	if err := registry.RegisterSchemaFS(assets, "schemas"); err != nil {
		return fmt.Errorf("register semantic reconciliation schema assets: %w", err)
	}
	return nil
}

// PromptHash returns the deterministic identity of the complete generic prompt.
func PromptHash() (string, error) {
	assets, err := assetFS()
	if err != nil {
		return "", err
	}
	parts := make([]llm.AssetHashPart, 0, len(promptFiles))
	for _, file := range promptFiles {
		parts = append(parts, llm.AssetHashPart{FS: assets, Path: file.Path})
	}
	return llm.HashAssets(parts)
}

// SchemaHash returns the deterministic identity of the response schema.
func SchemaHash() (string, error) {
	schema, err := LoadResponseSchema()
	if err != nil {
		return "", err
	}
	return schema.SHA256, nil
}

// SharedPromptFiles returns fresh descriptors for the mandatory protocol and
// variable-input presentation assets that domain prompts may reuse.
func SharedPromptFiles() ([]promptfs.SharedPromptFile, error) {
	assets, err := assetFS()
	if err != nil {
		return nil, err
	}
	return []promptfs.SharedPromptFile{
		{Name: "protocol.md", FS: assets, Path: "prompts/protocol.md"},
		{Name: "candidates.md", FS: assets, Path: "prompts/candidates.md"},
		{Name: "transcript-windows.md", FS: assets, Path: "prompts/transcript-windows.md"},
	}, nil
}

func ensureAssetsAbsent(registry *llm.AssetRegistry) error {
	prompts, err := registry.PromptFS()
	if err != nil {
		return fmt.Errorf("inspect registered prompt assets: %w", err)
	}
	if _, err := fs.Stat(prompts, PromptID+"/prompt.yaml"); err == nil {
		return fmt.Errorf("semantic reconciliation prompt assets already registered")
	} else if !errors.Is(err, fs.ErrNotExist) {
		return fmt.Errorf("inspect semantic reconciliation prompt assets: %w", err)
	}

	schemas, err := registry.SchemaFS()
	if err != nil {
		return fmt.Errorf("inspect registered schema assets: %w", err)
	}
	if _, err := fs.Stat(schemas, "semantic_reconciliation_llm.v1.json"); err == nil {
		return fmt.Errorf("semantic reconciliation schema assets already registered")
	} else if !errors.Is(err, fs.ErrNotExist) {
		return fmt.Errorf("inspect semantic reconciliation schema assets: %w", err)
	}
	return nil
}
