//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package filesystem

import (
	stdctx "context"
	"fmt"
	"strings"
	"time"
)

// Engine is the core of the Virtual Filesystem
// It manages providers and routes commands to the appropriate provider
type Engine struct {
	providers []Provider
}

// NewEngine creates a new Virtual Filesystem Engine
func NewEngine() *Engine {
	return &Engine{
		providers: make([]Provider, 0),
	}
}

// RegisterProvider registers a provider with the engine
func (e *Engine) RegisterProvider(provider Provider) {
	e.providers = append(e.providers, provider)
}

// GetProviders returns all registered providers
func (e *Engine) GetProviders() []ProviderInfo {
	infos := make([]ProviderInfo, 0, len(e.providers))
	for _, p := range e.providers {
		infos = append(infos, ProviderInfo{
			Name:        p.Name(),
			Description: p.Description(),
		})
	}
	return infos
}

// Execute executes a command and returns the result
func (e *Engine) Execute(ctx stdctx.Context, cmd *Command) (*Result, error) {
	switch cmd.Type {
	case CommandList:
		return e.List(ctx, cmd.Path, parseListOptions(cmd.Params))
	case CommandSearch:
		return e.Search(ctx, cmd.Path, parseSearchOptions(cmd.Params))
	case CommandCat:
		_, err := e.Cat(ctx, cmd.Path)
		return nil, err
	default:
		return nil, fmt.Errorf("unknown command type: %s", cmd.Type)
	}
}

// resolveProvider finds the provider for a given path
func (e *Engine) resolveProvider(path string) (Provider, string, error) {
	path = normalizePath(path)

	for _, provider := range e.providers {
		if provider.Supports(path) {
			// Parse the subpath relative to the provider root
			// Get provider name to calculate subPath
			providerName := provider.Name()
			var subPath string
			if path == providerName {
				subPath = ""
			} else if strings.HasPrefix(path, providerName+"/") {
				subPath = path[len(providerName)+1:]
			} else {
				subPath = path
			}
			return provider, subPath, nil
		}
	}

	// If no provider supports this path, check if FileProvider can handle it as a fallback
	// This allows paths like "myskills" to be treated as "files/myskills"
	if fileProvider := e.getFileProvider(); fileProvider != nil {
		// Check if the path looks like a file manager path (single component, not matching other providers)
		parts := SplitPath(path)
		if len(parts) > 0 && parts[0] != "datasets" {
			return fileProvider, path, nil
		}
	}

	return nil, "", fmt.Errorf("%s: %s", ErrProviderNotFound, path)
}

// List lists nodes at the given path
// If path is empty, returns:
//   1. Built-in providers (e.g., datasets)
//   2. Top-level directories from files provider (if any)
func (e *Engine) List(ctx stdctx.Context, path string, opts *ListOptions) (*Result, error) {
	// Normalize path
	path = normalizePath(path)

	// If path is empty, return list of providers and files root directories
	if path == "" || path == "/" {
		return e.listRoot(ctx, opts)
	}

	provider, subPath, err := e.resolveProvider(path)
	if err != nil {
		// If not found, try to find in files provider as a fallback
		// This allows "ls myfolder" to work as "ls files/myfolder"
		if fileProvider := e.getFileProvider(); fileProvider != nil {
			result, ferr := fileProvider.List(ctx, path, opts)
			if ferr == nil {
				return result, nil
			}
		}
		return nil, err
	}

	return provider.List(ctx, subPath, opts)
}

// listRoot returns the root listing:
// 1. Built-in providers (datasets, etc.)
// 2. Top-level folders from files provider (file_manager)
func (e *Engine) listRoot(ctx stdctx.Context, opts *ListOptions) (*Result, error) {
	nodes := make([]*Node, 0)
	// Track names to avoid duplicates
	seen := make(map[string]bool)

	// Add built-in providers first (like datasets)
	for _, p := range e.providers {
		// Skip files provider from this list - we'll add its children instead
		if p.Name() == "files" {
			continue
		}
		nodes = append(nodes, &Node{
			Name:      p.Name(),
			Path:      "/" + p.Name(),
			Type:      NodeTypeDirectory,
			CreatedAt: time.Now(),
			Metadata: map[string]interface{}{
				"description": p.Description(),
			},
		})
		seen[p.Name()] = true
	}

	// Add top-level folders from files provider (file_manager)
	if fileProvider := e.getFileProvider(); fileProvider != nil {
		filesResult, err := fileProvider.List(ctx, "", opts)
		if err == nil {
			for _, node := range filesResult.Nodes {
				// Only add folders (directories), not files
				if node.Type == NodeTypeDirectory {
					// Skip if already added by a provider
					if seen[node.Name] {
						continue
					}
					seen[node.Name] = true
					// Ensure path doesn't have /files/ prefix for display
					node.Path = strings.TrimPrefix(node.Path, "files/")
					node.Path = strings.TrimPrefix(node.Path, "/")
					nodes = append(nodes, node)
				}
			}
		}
	}

	return &Result{
		Nodes: nodes,
		Total: len(nodes),
	}, nil
}

// getFileProvider returns the files provider if registered
func (e *Engine) getFileProvider() Provider {
	for _, p := range e.providers {
		if p.Name() == "files" {
			return p
		}
	}
	return nil
}

// GetProvider returns a provider by name
func (e *Engine) GetProvider(name string) Provider {
	for _, p := range e.providers {
		if p.Name() == name {
			return p
		}
	}
	return nil
}

// Search searches for nodes matching the query
func (e *Engine) Search(ctx stdctx.Context, path string, opts *SearchOptions) (*Result, error) {
	provider, subPath, err := e.resolveProvider(path)
	if err != nil {
		return nil, err
	}

	return provider.Search(ctx, subPath, opts)
}

// Cat retrieves the content of a file/document
func (e *Engine) Cat(ctx stdctx.Context, path string) ([]byte, error) {
	provider, subPath, err := e.resolveProvider(path)
	if err != nil {
		// If not found, try to find in files provider as a fallback
		// This allows "cat myfolder/file.txt" to work as "cat files/myfolder/file.txt"
		if fileProvider := e.getFileProvider(); fileProvider != nil {
			return fileProvider.Cat(ctx, path)
		}
		return nil, err
	}

	return provider.Cat(ctx, subPath)
}

// ParsePath parses a path and returns path information
func (e *Engine) ParsePath(path string) (*PathInfo, error) {
	path = normalizePath(path)
	components := SplitPath(path)

	if len(components) == 0 {
		return nil, fmt.Errorf("empty path")
	}

	providerName := components[0]
	isRoot := len(components) == 1

	// Find the provider
	var provider Provider
	for _, p := range e.providers {
		if p.Name() == providerName || strings.HasPrefix(path, p.Name()) {
			provider = p
			break
		}
	}

	if provider == nil {
		return nil, fmt.Errorf("%s: %s", ErrProviderNotFound, path)
	}

	info := &PathInfo{
		Provider:   providerName,
		Path:       path,
		Components: components,
		IsRoot:     isRoot,
	}

	// Extract resource ID or name if available
	if len(components) >= 2 {
		info.ResourceName = components[1]
	}

	return info, nil
}

// parseListOptions parses command params into ListOptions
func parseListOptions(params map[string]interface{}) *ListOptions {
	opts := &ListOptions{}

	if params == nil {
		return opts
	}

	if recursive, ok := params["recursive"].(bool); ok {
		opts.Recursive = recursive
	}
	if limit, ok := params["limit"].(int); ok {
		opts.Limit = limit
	}
	if offset, ok := params["offset"].(int); ok {
		opts.Offset = offset
	}
	if sortBy, ok := params["sort_by"].(string); ok {
		opts.SortBy = sortBy
	}
	if sortOrder, ok := params["sort_order"].(string); ok {
		opts.SortOrder = sortOrder
	}

	return opts
}

// parseSearchOptions parses command params into SearchOptions
func parseSearchOptions(params map[string]interface{}) *SearchOptions {
	opts := &SearchOptions{}

	if params == nil {
		return opts
	}

	if query, ok := params["query"].(string); ok {
		opts.Query = query
	}
	if limit, ok := params["limit"].(int); ok {
		opts.Limit = limit
	}
	if offset, ok := params["offset"].(int); ok {
		opts.Offset = offset
	}
	if recursive, ok := params["recursive"].(bool); ok {
		opts.Recursive = recursive
	}
	if topK, ok := params["top_k"].(int); ok {
		opts.TopK = topK
	}
	if threshold, ok := params["threshold"].(float64); ok {
		opts.Threshold = threshold
	}
	if dirs, ok := params["dirs"].([]string); ok {
		opts.Dirs = dirs
	}

	return opts
}
