// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later

package org

import (
	"net/http"
	"path"
	"strings"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
	repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/gitrepo"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/markup/markdown"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
	shared_user "code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/web/shared/user"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)

const tplOrgWiki templates.TplName = "org/wiki/view"

// OrgWikiPage represents a single page in the org wiki sidebar.
type OrgWikiPage struct {
	Name   string
	SubURL string
}

// OrgWikiTreeNode represents a node in the org wiki folder tree for sidebar navigation.
type OrgWikiTreeNode struct {
	Name     string
	SubURL   string
	IsDir    bool
	Children []*OrgWikiTreeNode
}

// Wiki renders the org wiki tab.
func Wiki(ctx *context.Context) {
	org := ctx.Org.Organization

	if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
		ctx.ServerError("RenderUserOrgHeader", err)
		return
	}

	ctx.Data["PageIsViewWiki"] = true
	ctx.Data["Title"] = org.DisplayName() + " - Wiki"

	// Determine which wiki repo to use (public vs member).
	viewAs := ctx.FormString("view_as", util.Iif(ctx.Org.IsMember, "member", "public"))
	viewAsMember := viewAs == "member"

	wikiRepo, commit := findOrgWikiCommit(ctx, org.ID, util.Iif(viewAsMember, shared_user.RepoNameWikiPrivate, shared_user.RepoNameWikiPublic))
	if wikiRepo == nil && viewAsMember {
		// Fall back to public wiki if member wiki doesn't exist.
		wikiRepo, commit = findOrgWikiCommit(ctx, org.ID, shared_user.RepoNameWikiPublic)
		viewAsMember = false
	}
	if wikiRepo == nil && !viewAsMember {
		// Fall back to member wiki if public wiki doesn't exist.
		wikiRepo, commit = findOrgWikiCommit(ctx, org.ID, shared_user.RepoNameWikiPrivate)
		viewAsMember = true
	}

	ctx.Data["IsViewingWikiAsMember"] = viewAsMember

	// Check whether both repos exist (for the dropdown toggle).
	publicExists := shared_user.OrgWikiRepoExists(ctx, org.ID, shared_user.RepoNameWikiPublic)
	privateExists := shared_user.OrgWikiRepoExists(ctx, org.ID, shared_user.RepoNameWikiPrivate)
	ctx.Data["ShowWikiViewSelector"] = publicExists && privateExists && ctx.Org.IsMember

	if wikiRepo == nil || commit == nil {
		ctx.Data["WikiEmpty"] = true
		ctx.HTML(http.StatusOK, tplOrgWiki)
		return
	}
	ctx.Data["WikiRepoLink"] = wikiRepo.Link()

	// Build folder tree for sidebar navigation.
	wikiTree := buildOrgWikiTree(commit)
	ctx.Data["WikiTree"] = wikiTree

	// Determine which page to render.
	pageName := ctx.PathParamRaw("*")
	if pageName == "" {
		pageName = "Home"
	}
	ctx.Data["CurrentPage"] = pageName

	// Try to find the file: exact match, then with .md extension.
	blob := findWikiBlob(commit, pageName)
	if blob == nil {
		// Page not found — show empty state with page list.
		ctx.Data["WikiPageNotFound"] = true
		ctx.HTML(http.StatusOK, tplOrgWiki)
		return
	}

	content, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize)
	if err != nil {
		ctx.ServerError("GetBlobContent", err)
		return
	}

	rctx := renderhelper.NewRenderContextRepoFile(ctx, wikiRepo, renderhelper.RepoFileOptions{
		CurrentRefPath: path.Join("branch", util.PathEscapeSegments(wikiRepo.DefaultBranch)),
	})
	renderedContent, err := markdown.RenderString(rctx, content)
	if err != nil {
		log.Error("Failed to render org wiki page %q: %v", pageName, err)
		ctx.ServerError("RenderString", err)
		return
	}
	ctx.Data["WikiContent"] = renderedContent

	// Render _Sidebar if it exists.
	sidebarBlob := findWikiBlob(commit, "_Sidebar")
	if sidebarBlob != nil {
		sidebarContent, err := sidebarBlob.GetBlobContent(setting.UI.MaxDisplayFileSize)
		if err == nil {
			rendered, err := markdown.RenderString(rctx, sidebarContent)
			if err == nil {
				ctx.Data["WikiSidebarHTML"] = rendered
			}
		}
	}

	// Render _Footer if it exists.
	footerBlob := findWikiBlob(commit, "_Footer")
	if footerBlob != nil {
		footerContent, err := footerBlob.GetBlobContent(setting.UI.MaxDisplayFileSize)
		if err == nil {
			rendered, err := markdown.RenderString(rctx, footerContent)
			if err == nil {
				ctx.Data["WikiFooterHTML"] = rendered
			}
		}
	}

	ctx.HTML(http.StatusOK, tplOrgWiki)
}

// buildOrgWikiTree builds a hierarchical folder tree from the org wiki git repo.
// Shows up to 2 levels deep (folders and their immediate children).
func buildOrgWikiTree(commit *git.Commit) []*OrgWikiTreeNode {
	if commit == nil {
		return nil
	}
	entries, err := commit.ListEntries()
	if err != nil {
		return nil
	}

	var topLevel []*OrgWikiTreeNode

	for _, entry := range entries {
		name := entry.Name()
		if entry.IsDir() {
			node := &OrgWikiTreeNode{
				Name:   name,
				SubURL: name,
				IsDir:  true,
			}
			// List children of this directory (1 level deep).
			subTree := entry.Tree()
			if subTree != nil {
				children, _ := subTree.ListEntries()
				for _, child := range children {
					childName := child.Name()
					if child.IsDir() {
						node.Children = append(node.Children, &OrgWikiTreeNode{
							Name:   childName,
							SubURL: name + "/" + childName,
							IsDir:  true,
						})
					} else if isMarkdownFile(childName) {
						displayName := strings.TrimSuffix(childName, path.Ext(childName))
						if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
							continue
						}
						node.Children = append(node.Children, &OrgWikiTreeNode{
							Name:   displayName,
							SubURL: name + "/" + displayName,
							IsDir:  false,
						})
					}
				}
			}
			topLevel = append(topLevel, node)
		} else if isMarkdownFile(name) {
			displayName := strings.TrimSuffix(name, path.Ext(name))
			if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
				continue
			}
			topLevel = append(topLevel, &OrgWikiTreeNode{
				Name:   displayName,
				SubURL: displayName,
				IsDir:  false,
			})
		}
	}
	return topLevel
}

// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .mokogitea.wiki.git).
// Tries fallback repo names (.profile, .github) if the primary doesn't exist.
func findOrgWikiCommit(ctx *context.Context, orgID int64, repoName string) (*repo_model.Repository, *git.Commit) {
	namesToTry := shared_user.ProfileRepoFallbacks(repoName)

	var dbRepo *repo_model.Repository
	var err error
	for _, name := range namesToTry {
		dbRepo, err = repo_model.GetRepositoryByName(ctx, orgID, name)
		if err == nil {
			break
		}
		if !repo_model.IsErrRepoNotExist(err) {
			log.Error("findOrgWikiCommit: GetRepositoryByName(%d, %s): %v", orgID, name, err)
			return nil, nil
		}
	}
	if dbRepo == nil {
		return nil, nil
	}

	// Open the wiki git repo (.wiki.git sidecar), not the main repo.
	wikiGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, dbRepo.WikiStorageRepo())
	if err != nil {
		// Wiki repo doesn't exist yet — not an error, just no wiki.
		return nil, nil
	}

	branch := dbRepo.DefaultWikiBranch
	if branch == "" {
		branch = "main"
	}
	commit, err := wikiGitRepo.GetBranchCommit(branch)
	if err != nil {
		log.Error("findOrgWikiCommit: GetBranchCommit wiki(%s, %s): %v", dbRepo.FullName(), branch, err)
		return nil, nil
	}

	return dbRepo, commit
}

// findWikiBlob looks up a markdown file in the commit by name.
// Tries exact match first, then appends .md.
func findWikiBlob(commit *git.Commit, name string) *git.Blob {
	// Try exact match (e.g., "Home.md").
	if blob, _ := commit.GetBlobByPath(name); blob != nil {
		return blob
	}
	// Try with .md extension (e.g., "Home" → "Home.md").
	if blob, _ := commit.GetBlobByPath(name + ".md"); blob != nil {
		return blob
	}
	// Try with .markdown extension.
	if blob, _ := commit.GetBlobByPath(name + ".markdown"); blob != nil {
		return blob
	}
	return nil
}

// isMarkdownFile returns true if the filename looks like a markdown file.
func isMarkdownFile(name string) bool {
	ext := strings.ToLower(path.Ext(name))
	return ext == ".md" || ext == ".markdown"
}
