// Package api contains the Telegram Bot API types and method wrappers.
// Most of the package is generated by cmd/genapi from internal/spec/api.json;
// this file holds the runtime types that are intentionally hand-coded.
//
// InputFile carries either a local upload (Reader+Filename) or a reference
// to a previously-uploaded file (file_id) / URL Telegram can fetch. It is
// not a pure JSON type, so the codegen skips it (see runtimeTypes in
// cmd/genapi/emitter.go).
//
// ResponseParameters mirrors client.ResponseParameters so callers importing
// only `api` can access retry_after and migrate_to_chat_id without pulling
// in the client package.
package api

import (
	"bytes"
	"fmt"
	"github.com/goccy/go-json"
	"io"
	"strconv"
)

// InputFile carries either a file path (for upload) or a Telegram file_id
// / URL string (for reuse). When PathOrID names a local file, the request
// is sent as multipart/form-data; otherwise the value is sent inline.
type InputFile struct {
	// PathOrID is one of: an absolute or relative filesystem path, a
	// previously-uploaded Telegram file_id, or an HTTPS URL Telegram
	// can fetch.
	PathOrID string
	// Reader, when non-nil, is used as the file content (Filename names it).
	Reader io.Reader
	// Filename is the upload filename used when Reader is set.
	Filename string
}

// IsLocalUpload reports whether this InputFile triggers a multipart upload.
func (f *InputFile) IsLocalUpload() bool {
	if f == nil {
		return false
	}
	return f.Reader != nil
}

// ResponseParameters is the optional metadata Telegram includes on certain
// failures. The most common is RetryAfter (seconds) on 429 responses.
//
// https://core.telegram.org/bots/api#responseparameters
type ResponseParameters struct {
	MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
	RetryAfter      int   `json:"retry_after,omitempty"`
}

// ChatID identifies a chat by either numeric id or "@username". The Telegram
// Bot API spells the same field as either an integer or a string; ChatID
// preserves both forms with explicit constructors and a custom MarshalJSON
// so callers never see `any` at the source level.
type ChatID struct {
	int64Set bool
	intID    int64
	username string
}

// ChatIDFromInt builds a ChatID for a numeric chat identifier (e.g. -1001234567890).
func ChatIDFromInt(id int64) ChatID { return ChatID{int64Set: true, intID: id} }

// ChatIDFromUsername builds a ChatID for a public chat (e.g. "@channel").
// The leading "@" is required by Telegram for usernames.
func ChatIDFromUsername(name string) ChatID { return ChatID{username: name} }

// IsZero reports whether c carries no value.
func (c ChatID) IsZero() bool { return !c.int64Set && c.username == "" }

// String returns the wire form (decimal integer or "@name") for use in
// multipart bodies.
func (c ChatID) String() string {
	if c.int64Set {
		return strconv.FormatInt(c.intID, 10)
	}
	return c.username
}

// MarshalJSON emits either a JSON number (integer form) or a JSON string
// (@username form). Empty values marshal as "null".
func (c ChatID) MarshalJSON() ([]byte, error) {
	if c.int64Set {
		return []byte(strconv.FormatInt(c.intID, 10)), nil
	}
	if c.username != "" {
		return json.Marshal(c.username)
	}
	return []byte("null"), nil
}

// UnmarshalJSON accepts either a JSON number or a JSON string.
func (c *ChatID) UnmarshalJSON(data []byte) error {
	data = bytes.TrimSpace(data)
	if len(data) == 0 || bytes.Equal(data, []byte("null")) {
		*c = ChatID{}
		return nil
	}
	if data[0] == '"' {
		var s string
		if err := json.Unmarshal(data, &s); err != nil {
			return err
		}
		*c = ChatIDFromUsername(s)
		return nil
	}
	var n int64
	if err := json.Unmarshal(data, &n); err != nil {
		return fmt.Errorf("ChatID: %w", err)
	}
	*c = ChatIDFromInt(n)
	return nil
}

// MessageOrBool wraps the "Message or True" return shape Telegram uses on
// edit methods (editMessageText, editMessageCaption, etc.). When the bot
// edits a regular chat message, Message is non-nil; when it edits an
// inline message, OK is true.
type MessageOrBool struct {
	Message *Message
	OK      bool
}

// UnmarshalJSON decodes either {...} into Message or `true`/`false` into OK.
func (m *MessageOrBool) UnmarshalJSON(data []byte) error {
	data = bytes.TrimSpace(data)
	if len(data) == 0 {
		return nil
	}
	if data[0] == '{' {
		m.Message = new(Message)
		return json.Unmarshal(data, m.Message)
	}
	var b bool
	if err := json.Unmarshal(data, &b); err != nil {
		return fmt.Errorf("MessageOrBool: %w", err)
	}
	m.OK = b
	return nil
}
