package eclipse

import (
	"math"
	"time"

	"b612.me/astro/basic"
)

// SolarEclipsePathOptions 控制日食中心路径采样。
// SolarEclipsePathOptions controls central solar eclipse path sampling.
type SolarEclipsePathOptions struct {
	// Step 是基础时间采样步长；<=0 时使用 1 分钟。
	// Step is the base time step; values <= 0 use one minute.
	Step time.Duration
	// TargetSpacingKM 是相邻中心线点的最大目标地表距离；<=0 时不按距离加密。
	// TargetSpacingKM is the target maximum ground spacing between centerline points; values <= 0 disable spacing refinement.
	TargetSpacingKM float64
}

// SolarEclipsePathPoint 表示日食路径上的一个地理点。
// SolarEclipsePathPoint is one geographic point on a solar eclipse path.
type SolarEclipsePathPoint struct {
	// Time 时刻，保持用户输入时区, time in the input location.
	Time time.Time
	// Longitude 经度，东正西负, longitude in degrees, east positive.
	Longitude float64
	// Latitude 纬度，北正南负, latitude in degrees, north positive.
	Latitude float64
	// SunAltitude 太阳高度角，单位度, Sun altitude in degrees.
	SunAltitude float64
	// WidthKM 中心食带宽度，单位千米；仅中心线点有意义。
	// WidthKM is the central path width in kilometers; meaningful for centerline points.
	WidthKM float64
}

// SolarEclipsePath 表示一次中心日食的路径数据。
// SolarEclipsePath contains central solar eclipse path data.
type SolarEclipsePath struct {
	// Eclipse 是对应的全局日食信息, related global solar eclipse information.
	Eclipse SolarEclipseInfo
	// Greatest 是食甚点/最佳观测点, greatest eclipse point.
	Greatest SolarEclipsePathPoint
	// CenterLine 是中心线, central line.
	CenterLine []SolarEclipsePathPoint
	// NorthernLimit 是中心食带北界近似线, approximate northern limit of the central path.
	NorthernLimit []SolarEclipsePathPoint
	// SouthernLimit 是中心食带南界近似线, approximate southern limit of the central path.
	SouthernLimit []SolarEclipsePathPoint
	// Step 是实际采用的基础时间采样步长, effective base time step.
	Step time.Duration
	// TargetSpacingKM 是实际采用的目标空间采样距离，单位千米。
	// TargetSpacingKM is the effective target spacing in kilometers.
	TargetSpacingKM float64
}

// SolarEclipsePartialFootprintOptions 控制日食偏食半影足迹采样。
// SolarEclipsePartialFootprintOptions controls solar eclipse penumbral footprint sampling.
type SolarEclipsePartialFootprintOptions struct {
	// Step 是基础时间采样步长；<=0 时使用 5 分钟。
	// Step is the base time step; values <= 0 use five minutes.
	Step time.Duration
	// BoundaryPoints 是每个瞬时半影边界的角向采样点数；<=0 时使用 180。
	// BoundaryPoints is the angular sample count for each instantaneous penumbral boundary; values <= 0 use 180.
	BoundaryPoints int
	// CentralShadowStep 是本影/反本影瞬时足迹的采样步长；<=0 时不计算。
	// CentralShadowStep is the umbral/antumbral footprint step; values <= 0 disable it.
	CentralShadowStep time.Duration
}

// SolarEclipsePartialAreaOptions 是 SolarEclipsePartialFootprintOptions 的兼容别名。
// SolarEclipsePartialAreaOptions is a compatibility alias for SolarEclipsePartialFootprintOptions.
type SolarEclipsePartialAreaOptions = SolarEclipsePartialFootprintOptions

// SolarEclipsePartialFootprint 表示某一时刻的半影足迹边界。
// SolarEclipsePartialFootprint is the penumbral footprint boundary at one instant.
type SolarEclipsePartialFootprint struct {
	// Time 时刻，保持用户输入时区, time in the input location.
	Time time.Time
	// Boundaries 是半影边界分段；反经线或无效投影会拆成多段。
	// Boundaries are segmented penumbral boundary polylines, split at invalid projections or the antimeridian.
	Boundaries [][]SolarEclipsePathPoint
	// Closed 表示 Boundaries 是否构成一个闭合边界。
	// Closed indicates whether Boundaries form one closed boundary.
	Closed bool
}

// SolarEclipsePartialFootprintsInfo 表示一次日食的偏食半影足迹序列。
// SolarEclipsePartialFootprintsInfo contains penumbral footprint samples for a solar eclipse.
type SolarEclipsePartialFootprintsInfo struct {
	// Eclipse 是对应的全局日食信息, related global solar eclipse information.
	Eclipse SolarEclipseInfo
	// Footprints 是按时间采样的瞬时半影足迹, sampled instantaneous penumbral footprints.
	Footprints []SolarEclipsePartialFootprint
	// CentralShadowFootprints 是按时间采样的本影/反本影足迹。
	// CentralShadowFootprints are sampled umbral/antumbral footprints.
	CentralShadowFootprints []SolarEclipsePartialFootprint
	// P1-P4 是半影与地球的外切/内切接触点；不存在的内切点保持零值。
	// P1-P4 are external/internal penumbral contacts; absent internal contacts remain zero.
	P1 SolarEclipsePathPoint
	P2 SolarEclipsePathPoint
	P3 SolarEclipsePathPoint
	P4 SolarEclipsePathPoint
	// U1-U4 是本影/反本影与地球的外切/内切接触点；不存在时保持零值。
	// U1-U4 are external/internal umbral/antumbral contacts; absent contacts remain zero.
	U1 SolarEclipsePathPoint
	U2 SolarEclipsePathPoint
	U3 SolarEclipsePathPoint
	U4 SolarEclipsePathPoint
	// Step 是实际采用的基础时间采样步长, effective base time step.
	Step time.Duration
	// BoundaryPoints 是实际采用的边界角向采样点数。
	// BoundaryPoints is the effective angular sample count for each boundary.
	BoundaryPoints int
	// CentralShadowStep 是本影/反本影足迹的实际采样步长；0 表示未计算。
	// CentralShadowStep is the effective umbral/antumbral footprint step; zero means disabled.
	CentralShadowStep time.Duration
}

// SolarEclipsePartialAreaInfo 是 SolarEclipsePartialFootprintsInfo 的兼容别名。
// SolarEclipsePartialAreaInfo is a compatibility alias for SolarEclipsePartialFootprintsInfo.
type SolarEclipsePartialAreaInfo = SolarEclipsePartialFootprintsInfo

type solarEclipsePathCalculator func(float64, basic.SolarEclipsePathOptions) basic.SolarEclipsePathResult
type solarEclipsePartialFootprintsCalculator func(float64, basic.SolarEclipsePartialFootprintOptions) basic.SolarEclipsePartialFootprintsResult

// SolarEclipseCentralPath 计算指定日期附近的日食中心路径，默认使用 NASA bulletin Split-K 模型。
// SolarEclipseCentralPath computes the central path near the given date, using NASA bulletin Split-K by default.
func SolarEclipseCentralPath(date time.Time, options SolarEclipsePathOptions) (SolarEclipsePath, bool) {
	return SolarEclipseCentralPathNASABulletinSplitK(date, options)
}

// SolarEclipseCentralPathNASABulletinSplitK 使用 NASA bulletin Split-K 模型计算日食中心路径。
// SolarEclipseCentralPathNASABulletinSplitK computes the central path with the NASA bulletin Split-K model.
func SolarEclipseCentralPathNASABulletinSplitK(date time.Time, options SolarEclipsePathOptions) (SolarEclipsePath, bool) {
	return solarEclipseCentralPath(date, options, basic.SolarEclipseCentralPathNASABulletinSplitK)
}

// SolarEclipseCentralPathIAUSingleK 使用 IAU Single-K 模型计算日食中心路径。
// SolarEclipseCentralPathIAUSingleK computes the central path with the IAU Single-K model.
func SolarEclipseCentralPathIAUSingleK(date time.Time, options SolarEclipsePathOptions) (SolarEclipsePath, bool) {
	return solarEclipseCentralPath(date, options, basic.SolarEclipseCentralPathIAUSingleK)
}

// SolarEclipsePartialFootprints 计算指定日期附近的日食半影足迹，默认使用 NASA bulletin Split-K 模型。
// SolarEclipsePartialFootprints computes penumbral footprint samples near the given date, using NASA bulletin Split-K by default.
func SolarEclipsePartialFootprints(date time.Time, options SolarEclipsePartialFootprintOptions) (SolarEclipsePartialFootprintsInfo, bool) {
	return SolarEclipsePartialFootprintsNASABulletinSplitK(date, options)
}

// SolarEclipsePartialFootprintsNASABulletinSplitK 使用 NASA bulletin Split-K 模型计算日食半影足迹。
// SolarEclipsePartialFootprintsNASABulletinSplitK computes penumbral footprint samples with the NASA bulletin Split-K model.
func SolarEclipsePartialFootprintsNASABulletinSplitK(date time.Time, options SolarEclipsePartialFootprintOptions) (SolarEclipsePartialFootprintsInfo, bool) {
	return solarEclipsePartialFootprints(date, options, basic.SolarEclipsePartialFootprintsNASABulletinSplitK)
}

// SolarEclipsePartialFootprintsIAUSingleK 使用 IAU Single-K 模型计算日食半影足迹。
// SolarEclipsePartialFootprintsIAUSingleK computes penumbral footprint samples with the IAU Single-K model.
func SolarEclipsePartialFootprintsIAUSingleK(date time.Time, options SolarEclipsePartialFootprintOptions) (SolarEclipsePartialFootprintsInfo, bool) {
	return solarEclipsePartialFootprints(date, options, basic.SolarEclipsePartialFootprintsIAUSingleK)
}

// SolarEclipsePartialArea 计算半影足迹，是 SolarEclipsePartialFootprints 的兼容包装。
// SolarEclipsePartialArea computes penumbral footprint samples and is a compatibility wrapper for SolarEclipsePartialFootprints.
func SolarEclipsePartialArea(date time.Time, options SolarEclipsePartialAreaOptions) (SolarEclipsePartialAreaInfo, bool) {
	return SolarEclipsePartialFootprints(date, options)
}

// SolarEclipsePartialAreaNASABulletinSplitK 是 SolarEclipsePartialFootprintsNASABulletinSplitK 的兼容包装。
// SolarEclipsePartialAreaNASABulletinSplitK is a compatibility wrapper for SolarEclipsePartialFootprintsNASABulletinSplitK.
func SolarEclipsePartialAreaNASABulletinSplitK(date time.Time, options SolarEclipsePartialAreaOptions) (SolarEclipsePartialAreaInfo, bool) {
	return SolarEclipsePartialFootprintsNASABulletinSplitK(date, options)
}

// SolarEclipsePartialAreaIAUSingleK 是 SolarEclipsePartialFootprintsIAUSingleK 的兼容包装。
// SolarEclipsePartialAreaIAUSingleK is a compatibility wrapper for SolarEclipsePartialFootprintsIAUSingleK.
func SolarEclipsePartialAreaIAUSingleK(date time.Time, options SolarEclipsePartialAreaOptions) (SolarEclipsePartialAreaInfo, bool) {
	return SolarEclipsePartialFootprintsIAUSingleK(date, options)
}

func solarEclipseCentralPath(
	date time.Time,
	options SolarEclipsePathOptions,
	calculator solarEclipsePathCalculator,
) (SolarEclipsePath, bool) {
	location := date.Location()
	result := calculator(solarEclipseTimeToTTJDE(date), basicSolarEclipsePathOptions(options))
	if !result.Eclipse.HasCentral || len(result.CenterLine) == 0 {
		return SolarEclipsePath{}, false
	}

	path := SolarEclipsePath{
		Eclipse:         solarEclipseInfoFromBasic(result.Eclipse, location),
		Greatest:        solarEclipsePathPointFromBasic(result.Greatest, location),
		CenterLine:      solarEclipsePathPointsFromBasic(result.CenterLine, location),
		NorthernLimit:   solarEclipsePathPointsFromBasic(result.NorthernLimit, location),
		SouthernLimit:   solarEclipsePathPointsFromBasic(result.SouthernLimit, location),
		Step:            solarEclipsePathStepDuration(result.StepDays),
		TargetSpacingKM: result.TargetSpacingKM,
	}
	return path, true
}

func solarEclipsePartialFootprints(
	date time.Time,
	options SolarEclipsePartialFootprintOptions,
	calculator solarEclipsePartialFootprintsCalculator,
) (SolarEclipsePartialFootprintsInfo, bool) {
	location := date.Location()
	result := calculator(solarEclipseTimeToTTJDE(date), basicSolarEclipsePartialFootprintOptions(options))
	if !result.Eclipse.HasPartial || len(result.Footprints) == 0 {
		return SolarEclipsePartialFootprintsInfo{}, false
	}

	footprints := SolarEclipsePartialFootprintsInfo{
		Eclipse:                 solarEclipseInfoFromBasic(result.Eclipse, location),
		Footprints:              solarEclipsePartialFootprintsFromBasic(result.Footprints, location),
		CentralShadowFootprints: solarEclipsePartialFootprintsFromBasic(result.CentralShadowFootprints, location),
		P1:                      solarEclipseOptionalPathPointFromBasic(result.P1, location),
		P2:                      solarEclipseOptionalPathPointFromBasic(result.P2, location),
		P3:                      solarEclipseOptionalPathPointFromBasic(result.P3, location),
		P4:                      solarEclipseOptionalPathPointFromBasic(result.P4, location),
		U1:                      solarEclipseOptionalPathPointFromBasic(result.U1, location),
		U2:                      solarEclipseOptionalPathPointFromBasic(result.U2, location),
		U3:                      solarEclipseOptionalPathPointFromBasic(result.U3, location),
		U4:                      solarEclipseOptionalPathPointFromBasic(result.U4, location),
		Step:                    solarEclipsePathStepDuration(result.StepDays),
		BoundaryPoints:          result.BoundaryPoints,
		CentralShadowStep:       solarEclipsePathStepDuration(result.CentralShadowStepDays),
	}
	return footprints, true
}

func basicSolarEclipsePathOptions(options SolarEclipsePathOptions) basic.SolarEclipsePathOptions {
	basicOptions := basic.SolarEclipsePathOptions{
		TargetSpacingKM: options.TargetSpacingKM,
	}
	if options.Step > 0 {
		basicOptions.StepDays = options.Step.Hours() / 24
	}
	return basicOptions
}

func basicSolarEclipsePartialFootprintOptions(options SolarEclipsePartialFootprintOptions) basic.SolarEclipsePartialFootprintOptions {
	basicOptions := basic.SolarEclipsePartialFootprintOptions{
		BoundaryPoints: options.BoundaryPoints,
	}
	if options.Step > 0 {
		basicOptions.StepDays = options.Step.Hours() / 24
	}
	if options.CentralShadowStep > 0 {
		basicOptions.CentralShadowStepDays = options.CentralShadowStep.Hours() / 24
	}
	return basicOptions
}

func solarEclipsePathStepDuration(stepDays float64) time.Duration {
	return time.Duration(math.Round(stepDays * 24 * float64(time.Hour)))
}

func solarEclipsePathPointsFromBasic(points []basic.SolarEclipsePathPoint, location *time.Location) []SolarEclipsePathPoint {
	if len(points) == 0 {
		return nil
	}
	result := make([]SolarEclipsePathPoint, len(points))
	for i, point := range points {
		result[i] = solarEclipsePathPointFromBasic(point, location)
	}
	return result
}

func solarEclipsePathPointFromBasic(point basic.SolarEclipsePathPoint, location *time.Location) SolarEclipsePathPoint {
	return SolarEclipsePathPoint{
		Time:        solarEclipseTTJDEToTime(point.JDE, location),
		Longitude:   point.Longitude,
		Latitude:    point.Latitude,
		SunAltitude: point.SunAltitude,
		WidthKM:     point.WidthKM,
	}
}

func solarEclipseOptionalPathPointFromBasic(point basic.SolarEclipsePathPoint, location *time.Location) SolarEclipsePathPoint {
	if point.JDE == 0 {
		return SolarEclipsePathPoint{}
	}
	return solarEclipsePathPointFromBasic(point, location)
}

func solarEclipsePartialFootprintsFromBasic(
	footprints []basic.SolarEclipsePartialFootprint,
	location *time.Location,
) []SolarEclipsePartialFootprint {
	if len(footprints) == 0 {
		return nil
	}
	result := make([]SolarEclipsePartialFootprint, len(footprints))
	for i, footprint := range footprints {
		result[i] = SolarEclipsePartialFootprint{
			Time:       solarEclipseTTJDEToTime(footprint.JDE, location),
			Boundaries: solarEclipsePartialBoundariesFromBasic(footprint.Boundaries, location),
			Closed:     footprint.Closed,
		}
	}
	return result
}

func solarEclipsePartialBoundariesFromBasic(
	boundaries [][]basic.SolarEclipsePathPoint,
	location *time.Location,
) [][]SolarEclipsePathPoint {
	if len(boundaries) == 0 {
		return nil
	}
	result := make([][]SolarEclipsePathPoint, len(boundaries))
	for i, boundary := range boundaries {
		result[i] = solarEclipsePathPointsFromBasic(boundary, location)
	}
	return result
}
