// Package static implements the "static" source: a git-folder-backed
// deployable that can serve plain files or run a Deno backend. Builds
// an image from the cloned folder and runs one container.
//
// The full deploy pipeline is implemented inline in this package
// (deploy.go / teardown.go / reconcile.go). It operates directly on
// plugin.Workload + the containers / workload_env tables — there is no
// longer a synthetic static_sites row backing each workload.
//
// The legacy internal/staticsite package remains alive to serve the
// /api/sites/* HTTP routes and the existing static_sites table; this
// plugin does not depend on it for state, only for git-provider
// helpers and Deno scaffolding generation.
package static

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/alexei/tinyforge/internal/workload/plugin"
)

// Config is the per-workload source config blob. Mirrors the fields
// that used to live on the static_sites table, less anything moved to
// Workload (notification config, webhook secrets, public_face).
type Config struct {
	Provider       string `json:"provider"`         // "gitea" | "github" | "gitlab"; "" = autodetect
	BaseURL        string `json:"base_url"`         // e.g. https://git.example.com
	RepoOwner      string `json:"repo_owner"`
	RepoName       string `json:"repo_name"`
	Branch         string `json:"branch"`
	FolderPath     string `json:"folder_path"`      // path within repo
	AccessToken    string `json:"access_token"`     // encrypted; optional for public repos
	Mode           string `json:"mode"`             // "static" | "deno"
	RenderMarkdown bool   `json:"render_markdown"`
	StorageEnabled bool   `json:"storage_enabled"`
	StorageLimitMB int    `json:"storage_limit_mb"`
	// ReportCommitStatus, when true, pushes the deploy outcome back to the
	// git provider as a commit status (pending/success/failure) on the
	// deployed SHA. Best-effort — a reporting failure never fails a deploy.
	ReportCommitStatus bool `json:"report_commit_status"`

	// DeployStrategy selects how a redeploy cuts over. "" (default) and
	// "recreate" stop the old container before the new one comes up (a brief
	// downtime window). "blue-green" starts the new container alongside the
	// old, gates it, swaps the proxy route in place, then reaps the old —
	// zero-downtime under NPM. Validated via plugin.ValidateStrategy.
	DeployStrategy string `json:"deploy_strategy,omitempty"`
}

// effectiveStrategy resolves the configured strategy for the static source.
// Empty maps to recreate — the source's historical behavior. Storage-backed
// deno sites are forced to recreate even when blue-green is requested: a
// blue-green overlap would mount the same RW named volume into BOTH
// containers at once (a concurrent-writer window recreate never has, since
// recreate stops blue before green starts).
func effectiveStrategy(cfg Config) string {
	s := cfg.DeployStrategy
	if s == "" {
		s = plugin.StrategyRecreate
	}
	if s == plugin.StrategyBlueGreen && cfg.StorageEnabled && cfg.Mode == "deno" {
		return plugin.StrategyRecreate
	}
	return s
}

type source struct{}

// Eager registration — the deploy pipeline lives entirely inside this
// package now, so the kind is usable as soon as init() fires. No more
// "backend not wired" failure mode at deploy time.
func init() { plugin.RegisterSource(&source{}) }

func (*source) Kind() string { return "static" }

func (*source) SchemaSample() any {
	return Config{
		Provider:   "gitea",
		BaseURL:    "https://git.example.com",
		RepoOwner:  "owner",
		RepoName:   "pages",
		Branch:     "main",
		FolderPath: "",
		Mode:       "static",
	}
}

func (*source) Validate(cfg json.RawMessage) error {
	var c Config
	if len(cfg) == 0 {
		return fmt.Errorf("static source: config is required")
	}
	if err := json.Unmarshal(cfg, &c); err != nil {
		return fmt.Errorf("static source: invalid json: %w", err)
	}
	if strings.TrimSpace(c.RepoOwner) == "" || strings.TrimSpace(c.RepoName) == "" {
		return fmt.Errorf("static source: repo_owner and repo_name are required")
	}
	if c.Mode != "" && c.Mode != "static" && c.Mode != "deno" {
		return fmt.Errorf("static source: mode must be \"static\" or \"deno\"")
	}
	if err := plugin.ValidateStrategy(c.DeployStrategy, true); err != nil {
		return fmt.Errorf("static source: %w", err)
	}
	return nil
}

func (*source) Deploy(ctx context.Context, deps plugin.Deps, w plugin.Workload, intent plugin.DeploymentIntent) error {
	return deploy(ctx, deps, w, intent)
}

func (*source) Teardown(ctx context.Context, deps plugin.Deps, w plugin.Workload) error {
	return teardown(ctx, deps, w)
}

func (*source) Reconcile(ctx context.Context, deps plugin.Deps, w plugin.Workload) error {
	return reconcile(ctx, deps, w)
}
