package tools
import (
"context"
"database/sql"
"fmt"
"skraak/db"
"skraak/utils"
"strings"
)
type PatternInput struct {
ID *string `json:"id,omitempty"`
RecordSeconds *int `json:"record_seconds,omitempty"`
SleepSeconds *int `json:"sleep_seconds,omitempty"`
}
type PatternOutput struct {
Pattern db.CyclicRecordingPattern `json:"pattern"`
Message string `json:"message"`
}
func CreateOrUpdatePattern(
ctx context.Context,
input PatternInput,
) (PatternOutput, error) {
if input.ID != nil && strings.TrimSpace(*input.ID) != "" {
return updatePattern(ctx, input)
}
return createPattern(ctx, input)
}
func validateCreatePatternInput(input PatternInput) error {
if input.RecordSeconds == nil {
return fmt.Errorf("record_seconds is required when creating a pattern")
}
if input.SleepSeconds == nil {
return fmt.Errorf("sleep_seconds is required when creating a pattern")
}
if err := utils.ValidatePositive(*input.RecordSeconds, "record_seconds"); err != nil {
return err
}
return utils.ValidatePositive(*input.SleepSeconds, "sleep_seconds")
}
func findExistingPattern(ctx context.Context, tx *db.LoggedTx, recordSeconds, sleepSeconds int) (db.CyclicRecordingPattern, bool, error) {
var existingID string
err := tx.QueryRowContext(ctx,
"SELECT id FROM cyclic_recording_pattern WHERE record_s = ? AND sleep_s = ? AND active = true",
recordSeconds, sleepSeconds,
).Scan(&existingID)
if err == sql.ErrNoRows {
return db.CyclicRecordingPattern{}, false, nil
}
if err != nil {
return db.CyclicRecordingPattern{}, false, err
}
var pattern db.CyclicRecordingPattern
err = tx.QueryRowContext(ctx,
"SELECT id, record_s, sleep_s, created_at, last_modified, active FROM cyclic_recording_pattern WHERE id = ?",
existingID,
).Scan(&pattern.ID, &pattern.RecordS, &pattern.SleepS, &pattern.CreatedAt, &pattern.LastModified, &pattern.Active)
if err != nil {
return db.CyclicRecordingPattern{}, false, fmt.Errorf("failed to fetch existing pattern: %w", err)
}
return pattern, true, nil
}
func validateUpdatePatternInput(input PatternInput) error {
if err := utils.ValidateShortID(*input.ID, "pattern_id"); err != nil {
return err
}
if input.RecordSeconds != nil {
if err := utils.ValidatePositive(*input.RecordSeconds, "record_seconds"); err != nil {
return err
}
}
if input.SleepSeconds != nil {
if err := utils.ValidateNonNegative(*input.SleepSeconds, "sleep_seconds"); err != nil {
return err
}
}
return nil
}
func createPattern(ctx context.Context, input PatternInput) (PatternOutput, error) {
var output PatternOutput
if err := validateCreatePatternInput(input); err != nil {
return output, err
}
err := db.WithWriteTx(ctx, dbPath, "create_or_update_pattern", func(database *sql.DB, tx *db.LoggedTx) error {
existing, found, ferr := findExistingPattern(ctx, tx, *input.RecordSeconds, *input.SleepSeconds)
if ferr != nil {
return fmt.Errorf("failed to check for existing pattern: %w", ferr)
}
if found {
output.Pattern = existing
output.Message = fmt.Sprintf("Pattern already exists with ID %s (record %ds, sleep %ds) - returning existing pattern",
existing.ID, existing.RecordS, existing.SleepS)
return nil }
id, gerr := utils.GenerateShortID()
if gerr != nil {
return fmt.Errorf("failed to generate ID: %w", gerr)
}
if _, err := tx.ExecContext(ctx,
"INSERT INTO cyclic_recording_pattern (id, record_s, sleep_s, created_at, last_modified, active) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, TRUE)",
id, *input.RecordSeconds, *input.SleepSeconds,
); err != nil {
return fmt.Errorf("failed to create pattern: %w", err)
}
var pattern db.CyclicRecordingPattern
if err := tx.QueryRowContext(ctx,
"SELECT id, record_s, sleep_s, created_at, last_modified, active FROM cyclic_recording_pattern WHERE id = ?",
id,
).Scan(&pattern.ID, &pattern.RecordS, &pattern.SleepS, &pattern.CreatedAt, &pattern.LastModified, &pattern.Active); err != nil {
return fmt.Errorf("failed to fetch created pattern: %w", err)
}
output.Pattern = pattern
output.Message = fmt.Sprintf("Successfully created cyclic recording pattern with ID %s (record %ds, sleep %ds)",
pattern.ID, pattern.RecordS, pattern.SleepS)
return nil
})
return output, err
}
func verifyPatternExistsAndActive(database *sql.DB, patternID string) error {
var exists, active bool
err := database.QueryRow(
"SELECT EXISTS(SELECT 1 FROM cyclic_recording_pattern WHERE id = ?), COALESCE((SELECT active FROM cyclic_recording_pattern WHERE id = ?), false)",
patternID, patternID,
).Scan(&exists, &active)
if err != nil {
return fmt.Errorf("failed to query pattern: %w", err)
}
if !exists {
return fmt.Errorf("pattern not found: %s", patternID)
}
if !active {
return fmt.Errorf("pattern '%s' is not active (cannot update inactive patterns)", patternID)
}
return nil
}
func buildPatternUpdateQuery(input PatternInput) (string, []any, error) {
updates := []string{}
args := []any{}
if input.RecordSeconds != nil {
updates = append(updates, "record_s = ?")
args = append(args, *input.RecordSeconds)
}
if input.SleepSeconds != nil {
updates = append(updates, "sleep_s = ?")
args = append(args, *input.SleepSeconds)
}
if len(updates) == 0 {
return "", nil, fmt.Errorf("no fields provided to update")
}
updates = append(updates, "last_modified = now()")
args = append(args, *input.ID)
query := fmt.Sprintf("UPDATE cyclic_recording_pattern SET %s WHERE id = ?", strings.Join(updates, ", "))
return query, args, nil
}
func updatePattern(ctx context.Context, input PatternInput) (PatternOutput, error) {
var output PatternOutput
if err := validateUpdatePatternInput(input); err != nil {
return output, err
}
err := db.WithWriteTx(ctx, dbPath, "create_or_update_pattern", func(database *sql.DB, tx *db.LoggedTx) error {
if err := verifyPatternExistsAndActive(database, *input.ID); err != nil {
return err
}
query, args, qerr := buildPatternUpdateQuery(input)
if qerr != nil {
return qerr
}
if _, err := tx.Exec(query, args...); err != nil {
return fmt.Errorf("failed to update pattern: %w", err)
}
var pattern db.CyclicRecordingPattern
if err := tx.QueryRow(
"SELECT id, record_s, sleep_s, created_at, last_modified, active FROM cyclic_recording_pattern WHERE id = ?",
*input.ID,
).Scan(&pattern.ID, &pattern.RecordS, &pattern.SleepS, &pattern.CreatedAt, &pattern.LastModified, &pattern.Active); err != nil {
return fmt.Errorf("failed to fetch updated pattern: %w", err)
}
output.Pattern = pattern
output.Message = fmt.Sprintf("Successfully updated pattern (ID: %s, record %ds, sleep %ds)",
pattern.ID, pattern.RecordS, pattern.SleepS)
return nil
})
return output, err
}