// Copyright 2024 The Hugo 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 commands

import (
	"context"
	"encoding/csv"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/bep/simplecobra"
	"github.com/gohugoio/hugo/hugolib"
	"github.com/gohugoio/hugo/resources/page"
	"github.com/gohugoio/hugo/resources/resource"
	"github.com/spf13/cobra"
)

// newListCommand creates a new list command and its subcommands.
func newListCommand() *listCommand {
	createRecord := func(workingDir string, p page.Page) []string {
		return []string{
			filepath.ToSlash(strings.TrimPrefix(p.File().Filename(), workingDir+string(os.PathSeparator))),
			p.Slug(),
			p.Title(),
			p.Date().Format(time.RFC3339),
			p.ExpiryDate().Format(time.RFC3339),
			p.PublishDate().Format(time.RFC3339),
			strconv.FormatBool(p.Draft()),
			p.Permalink(),
			p.Kind(),
			p.Section(),
		}
	}

	list := func(cd *simplecobra.Commandeer, r *rootCommand, shouldInclude func(page.Page) bool, opts ...any) error {
		bcfg := hugolib.BuildCfg{SkipRender: true}
		cfg := flagsToCfg(cd, nil)
		for i := 0; i < len(opts); i += 2 {
			cfg.Set(opts[i].(string), opts[i+1])
		}
		h, err := r.Build(cd, bcfg, cfg)
		if err != nil {
			return err
		}

		writer := csv.NewWriter(r.StdOut)
		defer writer.Flush()

		writer.Write([]string{
			"path",
			"slug",
			"title",
			"date",
			"expiryDate",
			"publishDate",
			"draft",
			"permalink",
			"kind",
			"section",
		})

		for _, p := range h.Pages() {
			if shouldInclude(p) {
				record := createRecord(h.Conf.BaseConfig().WorkingDir, p)
				if err := writer.Write(record); err != nil {
					return err
				}
			}
		}

		return nil
	}

	return &listCommand{
		commands: []simplecobra.Commander{
			&simpleCommand{
				name:  "drafts",
				short: "List draft content",
				long:  `List draft content.`,
				run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
					shouldInclude := func(p page.Page) bool {
						if !p.Draft() || p.File() == nil {
							return false
						}
						return true
					}
					return list(cd, r, shouldInclude,
						"buildDrafts", true,
						"buildFuture", true,
						"buildExpired", true,
					)
				},
				withc: func(cmd *cobra.Command, r *rootCommand) {
					cmd.ValidArgsFunction = cobra.NoFileCompletions
				},
			},
			&simpleCommand{
				name:  "future",
				short: "List future content",
				long:  `List content with a future publication date.`,
				run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
					shouldInclude := func(p page.Page) bool {
						if !resource.IsFuture(p) || p.File() == nil {
							return false
						}
						return true
					}
					return list(cd, r, shouldInclude,
						"buildFuture", true,
						"buildDrafts", true,
					)
				},
				withc: func(cmd *cobra.Command, r *rootCommand) {
					cmd.ValidArgsFunction = cobra.NoFileCompletions
				},
			},
			&simpleCommand{
				name:  "expired",
				short: "List expired content",
				long:  `List content with a past expiration date.`,
				run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
					shouldInclude := func(p page.Page) bool {
						if !resource.IsExpired(p) || p.File() == nil {
							return false
						}
						return true
					}
					return list(cd, r, shouldInclude,
						"buildExpired", true,
						"buildDrafts", true,
					)
				},
				withc: func(cmd *cobra.Command, r *rootCommand) {
					cmd.ValidArgsFunction = cobra.NoFileCompletions
				},
			},
			&simpleCommand{
				name:  "all",
				short: "List all content",
				long:  `List all content including draft, future, and expired.`,
				run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
					shouldInclude := func(p page.Page) bool {
						return p.File() != nil
					}
					return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
				},
				withc: func(cmd *cobra.Command, r *rootCommand) {
					cmd.ValidArgsFunction = cobra.NoFileCompletions
				},
			},
			&simpleCommand{
				name:  "published",
				short: "List published content",
				long:  `List content that is not draft, future, or expired.`,
				run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
					shouldInclude := func(p page.Page) bool {
						return !p.Draft() && !resource.IsFuture(p) && !resource.IsExpired(p) && p.File() != nil
					}
					return list(cd, r, shouldInclude)
				},
				withc: func(cmd *cobra.Command, r *rootCommand) {
					cmd.ValidArgsFunction = cobra.NoFileCompletions
				},
			},
		},
	}
}

type listCommand struct {
	commands []simplecobra.Commander
}

func (c *listCommand) Commands() []simplecobra.Commander {
	return c.commands
}

func (c *listCommand) Name() string {
	return "list"
}

func (c *listCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
	// Do nothing.
	return nil
}

func (c *listCommand) Init(cd *simplecobra.Commandeer) error {
	cmd := cd.CobraCommand
	cmd.Short = "List content"
	cmd.Long = `List content.

List requires a subcommand, e.g. hugo list drafts`

	cmd.RunE = nil
	return nil
}

func (c *listCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
	return nil
}
