package shared

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"gitea.maximumdirect.net/eric/notarius/internal/core/source"
)

type UnitRef struct {
	value      int
	fromNumber bool
}

type SourceRefResponse struct {
	SourceID    string  `json:"source_id"`
	StartUnitID UnitRef `json:"start_unit_id"`
	EndUnitID   UnitRef `json:"end_unit_id"`
}

func UnitRefFromInt(value int) UnitRef {
	return UnitRef{
		value:      value,
		fromNumber: true,
	}
}

func (ref UnitRef) String() string {
	if ref.value == 0 {
		return ""
	}
	return strconv.Itoa(ref.value)
}

func (ref UnitRef) Int() int {
	return ref.value
}

func (ref *UnitRef) UnmarshalJSON(raw []byte) error {
	raw = bytes.TrimSpace(raw)
	if len(raw) == 0 {
		return fmt.Errorf("unit ref must be a string or integer")
	}
	if raw[0] == '"' {
		var value string
		if err := json.Unmarshal(raw, &value); err != nil {
			return err
		}
		number, err := parseUnitRefNumber(value)
		if err != nil {
			return err
		}
		*ref = UnitRef{value: number}
		return nil
	}

	number, err := parseUnitRefNumber(string(raw))
	if err != nil {
		return err
	}
	*ref = UnitRefFromInt(number)
	return nil
}

func (ref UnitRef) MarshalJSON() ([]byte, error) {
	if ref.fromNumber {
		return []byte(strconv.Itoa(ref.value)), nil
	}
	return json.Marshal(ref.String())
}

func ResolveUnitID(index source.DocumentIndex, field string, ref UnitRef) (int, error) {
	if ref.value <= 0 {
		return 0, fmt.Errorf("%s must be positive", field)
	}
	if _, ok := index.Position(ref.value); !ok {
		return 0, fmt.Errorf("%s %d was not found", field, ref.value)
	}
	return ref.value, nil
}

func parseUnitRefNumber(value string) (int, error) {
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return 0, fmt.Errorf("unit ref must not be empty")
	}
	if trimmed != value {
		return 0, fmt.Errorf("unit ref must not contain leading or trailing whitespace")
	}
	number, err := strconv.Atoi(value)
	if err != nil {
		return 0, fmt.Errorf("unit ref must be an integer")
	}
	if number <= 0 {
		return 0, fmt.Errorf("unit ref must be positive")
	}
	return number, nil
}
